fixed problems with current level set node being a tree node copy
[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   // in a number of cases, tree node is no valid level set
1158   if (node == NULL || node->node_group || node->parent_link || node->is_copy)
1159     return FALSE;
1160
1161   return TRUE;
1162 }
1163
1164 TreeInfo *getValidLevelSeries(TreeInfo *node, TreeInfo *default_node)
1165 {
1166   if (validLevelSeries(node))
1167     return node;
1168   else if (node->is_copy)
1169     return getTreeInfoFromIdentifier(leveldir_first, node->identifier);
1170   else
1171     return getFirstValidTreeInfoEntry(default_node);
1172 }
1173
1174 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1175 {
1176   if (node == NULL)
1177     return NULL;
1178
1179   if (node->node_group)         // enter level group (step down into tree)
1180     return getFirstValidTreeInfoEntry(node->node_group);
1181   else if (node->parent_link)   // skip start entry of level group
1182   {
1183     if (node->next)             // get first real level series entry
1184       return getFirstValidTreeInfoEntry(node->next);
1185     else                        // leave empty level group and go on
1186       return getFirstValidTreeInfoEntry(node->node_parent->next);
1187   }
1188   else                          // this seems to be a regular level series
1189     return node;
1190 }
1191
1192 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1193 {
1194   if (node == NULL)
1195     return NULL;
1196
1197   if (node->node_parent == NULL)                // top level group
1198     return *node->node_top;
1199   else                                          // sub level group
1200     return node->node_parent->node_group;
1201 }
1202
1203 int numTreeInfoInGroup(TreeInfo *node)
1204 {
1205   return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1206 }
1207
1208 int getPosFromTreeInfo(TreeInfo *node)
1209 {
1210   TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1211   int pos = 0;
1212
1213   while (node_cmp)
1214   {
1215     if (node_cmp == node)
1216       return pos;
1217
1218     pos++;
1219     node_cmp = node_cmp->next;
1220   }
1221
1222   return 0;
1223 }
1224
1225 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1226 {
1227   TreeInfo *node_default = node;
1228   int pos_cmp = 0;
1229
1230   while (node)
1231   {
1232     if (pos_cmp == pos)
1233       return node;
1234
1235     pos_cmp++;
1236     node = node->next;
1237   }
1238
1239   return node_default;
1240 }
1241
1242 static TreeInfo *getTreeInfoFromIdentifierExt(TreeInfo *node, char *identifier,
1243                                               boolean include_node_groups)
1244 {
1245   if (identifier == NULL)
1246     return NULL;
1247
1248   while (node)
1249   {
1250     if (node->node_group)
1251     {
1252       if (include_node_groups && strEqual(identifier, node->identifier))
1253         return node;
1254
1255       TreeInfo *node_group = getTreeInfoFromIdentifierExt(node->node_group,
1256                                                           identifier,
1257                                                           include_node_groups);
1258       if (node_group)
1259         return node_group;
1260     }
1261     else if (!node->parent_link && !node->is_copy)
1262     {
1263       if (strEqual(identifier, node->identifier))
1264         return node;
1265     }
1266
1267     node = node->next;
1268   }
1269
1270   return NULL;
1271 }
1272
1273 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1274 {
1275   return getTreeInfoFromIdentifierExt(node, identifier, FALSE);
1276 }
1277
1278 static TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1279                                TreeInfo *node, boolean skip_sets_without_levels)
1280 {
1281   TreeInfo *node_new;
1282
1283   if (node == NULL)
1284     return NULL;
1285
1286   if (!node->parent_link && !node->level_group &&
1287       skip_sets_without_levels && node->levels == 0)
1288     return cloneTreeNode(node_top, node_parent, node->next,
1289                          skip_sets_without_levels);
1290
1291   node_new = getTreeInfoCopy(node);             // copy complete node
1292
1293   node_new->node_top = node_top;                // correct top node link
1294   node_new->node_parent = node_parent;          // correct parent node link
1295
1296   if (node->level_group)
1297     node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1298                                          skip_sets_without_levels);
1299
1300   node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1301                                  skip_sets_without_levels);
1302   
1303   return node_new;
1304 }
1305
1306 static void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1307 {
1308   TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1309
1310   *ti_new = ti_cloned;
1311 }
1312
1313 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1314 {
1315   boolean settings_changed = FALSE;
1316
1317   while (node)
1318   {
1319     boolean want_ecs = (setup.prefer_aga_graphics == FALSE);
1320     boolean want_aga = (setup.prefer_aga_graphics == TRUE);
1321     boolean has_only_ecs = (!node->graphics_set && !node->graphics_set_aga);
1322     boolean has_only_aga = (!node->graphics_set && !node->graphics_set_ecs);
1323     char *graphics_set = NULL;
1324
1325     if (node->graphics_set_ecs && (want_ecs || has_only_ecs))
1326       graphics_set = node->graphics_set_ecs;
1327
1328     if (node->graphics_set_aga && (want_aga || has_only_aga))
1329       graphics_set = node->graphics_set_aga;
1330
1331     if (graphics_set && !strEqual(node->graphics_set, graphics_set))
1332     {
1333       setString(&node->graphics_set, graphics_set);
1334       settings_changed = TRUE;
1335     }
1336
1337     if (node->node_group != NULL)
1338       settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1339
1340     node = node->next;
1341   }
1342
1343   return settings_changed;
1344 }
1345
1346 static boolean adjustTreeSoundsForEMC(TreeInfo *node)
1347 {
1348   boolean settings_changed = FALSE;
1349
1350   while (node)
1351   {
1352     boolean want_default = (setup.prefer_lowpass_sounds == FALSE);
1353     boolean want_lowpass = (setup.prefer_lowpass_sounds == TRUE);
1354     boolean has_only_default = (!node->sounds_set && !node->sounds_set_lowpass);
1355     boolean has_only_lowpass = (!node->sounds_set && !node->sounds_set_default);
1356     char *sounds_set = NULL;
1357
1358     if (node->sounds_set_default && (want_default || has_only_default))
1359       sounds_set = node->sounds_set_default;
1360
1361     if (node->sounds_set_lowpass && (want_lowpass || has_only_lowpass))
1362       sounds_set = node->sounds_set_lowpass;
1363
1364     if (sounds_set && !strEqual(node->sounds_set, sounds_set))
1365     {
1366       setString(&node->sounds_set, sounds_set);
1367       settings_changed = TRUE;
1368     }
1369
1370     if (node->node_group != NULL)
1371       settings_changed |= adjustTreeSoundsForEMC(node->node_group);
1372
1373     node = node->next;
1374   }
1375
1376   return settings_changed;
1377 }
1378
1379 void dumpTreeInfo(TreeInfo *node, int depth)
1380 {
1381   char bullet_list[] = { '-', '*', 'o' };
1382   int i;
1383
1384   if (depth == 0)
1385     Debug("tree", "Dumping TreeInfo:");
1386
1387   while (node)
1388   {
1389     char bullet = bullet_list[depth % ARRAY_SIZE(bullet_list)];
1390
1391     for (i = 0; i < depth * 2; i++)
1392       DebugContinued("", " ");
1393
1394     DebugContinued("tree", "%c '%s' ['%s] [PARENT: '%s'] %s\n",
1395                    bullet, node->name, node->identifier,
1396                    (node->node_parent ? node->node_parent->identifier : "-"),
1397                    (node->node_group ? "[GROUP]" : ""));
1398
1399     /*
1400     // use for dumping artwork info tree
1401     Debug("tree", "subdir == '%s' ['%s', '%s'] [%d])",
1402           node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1403     */
1404
1405     if (node->node_group != NULL)
1406       dumpTreeInfo(node->node_group, depth + 1);
1407
1408     node = node->next;
1409   }
1410 }
1411
1412 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1413                                 int (*compare_function)(const void *,
1414                                                         const void *))
1415 {
1416   int num_nodes = numTreeInfo(*node_first);
1417   TreeInfo **sort_array;
1418   TreeInfo *node = *node_first;
1419   int i = 0;
1420
1421   if (num_nodes == 0)
1422     return;
1423
1424   // allocate array for sorting structure pointers
1425   sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1426
1427   // writing structure pointers to sorting array
1428   while (i < num_nodes && node)         // double boundary check...
1429   {
1430     sort_array[i] = node;
1431
1432     i++;
1433     node = node->next;
1434   }
1435
1436   // sorting the structure pointers in the sorting array
1437   qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1438         compare_function);
1439
1440   // update the linkage of list elements with the sorted node array
1441   for (i = 0; i < num_nodes - 1; i++)
1442     sort_array[i]->next = sort_array[i + 1];
1443   sort_array[num_nodes - 1]->next = NULL;
1444
1445   // update the linkage of the main list anchor pointer
1446   *node_first = sort_array[0];
1447
1448   free(sort_array);
1449
1450   // now recursively sort the level group structures
1451   node = *node_first;
1452   while (node)
1453   {
1454     if (node->node_group != NULL)
1455       sortTreeInfoBySortFunction(&node->node_group, compare_function);
1456
1457     node = node->next;
1458   }
1459 }
1460
1461 void sortTreeInfo(TreeInfo **node_first)
1462 {
1463   sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1464 }
1465
1466
1467 // ============================================================================
1468 // some stuff from "files.c"
1469 // ============================================================================
1470
1471 #if defined(PLATFORM_WIN32)
1472 #ifndef S_IRGRP
1473 #define S_IRGRP S_IRUSR
1474 #endif
1475 #ifndef S_IROTH
1476 #define S_IROTH S_IRUSR
1477 #endif
1478 #ifndef S_IWGRP
1479 #define S_IWGRP S_IWUSR
1480 #endif
1481 #ifndef S_IWOTH
1482 #define S_IWOTH S_IWUSR
1483 #endif
1484 #ifndef S_IXGRP
1485 #define S_IXGRP S_IXUSR
1486 #endif
1487 #ifndef S_IXOTH
1488 #define S_IXOTH S_IXUSR
1489 #endif
1490 #ifndef S_IRWXG
1491 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1492 #endif
1493 #ifndef S_ISGID
1494 #define S_ISGID 0
1495 #endif
1496 #endif  // PLATFORM_WIN32
1497
1498 // file permissions for newly written files
1499 #define MODE_R_ALL              (S_IRUSR | S_IRGRP | S_IROTH)
1500 #define MODE_W_ALL              (S_IWUSR | S_IWGRP | S_IWOTH)
1501 #define MODE_X_ALL              (S_IXUSR | S_IXGRP | S_IXOTH)
1502
1503 #define MODE_W_PRIVATE          (S_IWUSR)
1504 #define MODE_W_PUBLIC_FILE      (S_IWUSR | S_IWGRP)
1505 #define MODE_W_PUBLIC_DIR       (S_IWUSR | S_IWGRP | S_ISGID)
1506
1507 #define DIR_PERMS_PRIVATE       (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1508 #define DIR_PERMS_PUBLIC        (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1509 #define DIR_PERMS_PUBLIC_ALL    (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1510
1511 #define FILE_PERMS_PRIVATE      (MODE_R_ALL | MODE_W_PRIVATE)
1512 #define FILE_PERMS_PUBLIC       (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1513 #define FILE_PERMS_PUBLIC_ALL   (MODE_R_ALL | MODE_W_ALL)
1514
1515
1516 char *getHomeDir(void)
1517 {
1518   static char *dir = NULL;
1519
1520 #if defined(PLATFORM_WIN32)
1521   if (dir == NULL)
1522   {
1523     dir = checked_malloc(MAX_PATH + 1);
1524
1525     if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1526       strcpy(dir, ".");
1527   }
1528 #elif defined(PLATFORM_EMSCRIPTEN)
1529   dir = "/persistent";
1530 #elif defined(PLATFORM_UNIX)
1531   if (dir == NULL)
1532   {
1533     if ((dir = getenv("HOME")) == NULL)
1534     {
1535       dir = getUnixHomeDir();
1536
1537       if (dir != NULL)
1538         dir = getStringCopy(dir);
1539       else
1540         dir = ".";
1541     }
1542   }
1543 #else
1544   dir = ".";
1545 #endif
1546
1547   return dir;
1548 }
1549
1550 char *getCommonDataDir(void)
1551 {
1552   static char *common_data_dir = NULL;
1553
1554 #if defined(PLATFORM_WIN32)
1555   if (common_data_dir == NULL)
1556   {
1557     char *dir = checked_malloc(MAX_PATH + 1);
1558
1559     if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1560         && !strEqual(dir, ""))          // empty for Windows 95/98
1561       common_data_dir = getPath2(dir, program.userdata_subdir);
1562     else
1563       common_data_dir = options.rw_base_directory;
1564   }
1565 #else
1566   if (common_data_dir == NULL)
1567     common_data_dir = options.rw_base_directory;
1568 #endif
1569
1570   return common_data_dir;
1571 }
1572
1573 char *getPersonalDataDir(void)
1574 {
1575   static char *personal_data_dir = NULL;
1576
1577 #if defined(PLATFORM_MACOSX)
1578   if (personal_data_dir == NULL)
1579     personal_data_dir = getPath2(getHomeDir(), "Documents");
1580 #else
1581   if (personal_data_dir == NULL)
1582     personal_data_dir = getHomeDir();
1583 #endif
1584
1585   return personal_data_dir;
1586 }
1587
1588 char *getMainUserGameDataDir(void)
1589 {
1590   static char *main_user_data_dir = NULL;
1591
1592 #if defined(PLATFORM_ANDROID)
1593   if (main_user_data_dir == NULL)
1594     main_user_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1595                                   SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1596                                   SDL_AndroidGetExternalStoragePath() :
1597                                   SDL_AndroidGetInternalStoragePath());
1598 #else
1599   if (main_user_data_dir == NULL)
1600     main_user_data_dir = getPath2(getPersonalDataDir(),
1601                                   program.userdata_subdir);
1602 #endif
1603
1604   return main_user_data_dir;
1605 }
1606
1607 char *getUserGameDataDir(void)
1608 {
1609   if (user.nr == 0)
1610     return getMainUserGameDataDir();
1611   else
1612     return getUserDir(user.nr);
1613 }
1614
1615 char *getSetupDir(void)
1616 {
1617   return getUserGameDataDir();
1618 }
1619
1620 static mode_t posix_umask(mode_t mask)
1621 {
1622 #if defined(PLATFORM_UNIX)
1623   return umask(mask);
1624 #else
1625   return 0;
1626 #endif
1627 }
1628
1629 static int posix_mkdir(const char *pathname, mode_t mode)
1630 {
1631 #if defined(PLATFORM_WIN32)
1632   return mkdir(pathname);
1633 #else
1634   return mkdir(pathname, mode);
1635 #endif
1636 }
1637
1638 static boolean posix_process_running_setgid(void)
1639 {
1640 #if defined(PLATFORM_UNIX)
1641   return (getgid() != getegid());
1642 #else
1643   return FALSE;
1644 #endif
1645 }
1646
1647 void createDirectory(char *dir, char *text, int permission_class)
1648 {
1649   if (directoryExists(dir))
1650     return;
1651
1652   // leave "other" permissions in umask untouched, but ensure group parts
1653   // of USERDATA_DIR_MODE are not masked
1654   mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1655                      DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1656   mode_t last_umask = posix_umask(0);
1657   mode_t group_umask = ~(dir_mode & S_IRWXG);
1658   int running_setgid = posix_process_running_setgid();
1659
1660   if (permission_class == PERMS_PUBLIC)
1661   {
1662     // if we're setgid, protect files against "other"
1663     // else keep umask(0) to make the dir world-writable
1664
1665     if (running_setgid)
1666       posix_umask(last_umask & group_umask);
1667     else
1668       dir_mode = DIR_PERMS_PUBLIC_ALL;
1669   }
1670
1671   if (posix_mkdir(dir, dir_mode) != 0)
1672     Warn("cannot create %s directory '%s': %s", text, dir, strerror(errno));
1673
1674   if (permission_class == PERMS_PUBLIC && !running_setgid)
1675     chmod(dir, dir_mode);
1676
1677   posix_umask(last_umask);              // restore previous umask
1678 }
1679
1680 void InitMainUserDataDirectory(void)
1681 {
1682   createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1683 }
1684
1685 void InitUserDataDirectory(void)
1686 {
1687   createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1688
1689   if (user.nr != 0)
1690   {
1691     createDirectory(getUserDir(-1), "users", PERMS_PRIVATE);
1692     createDirectory(getUserDir(user.nr), "user data", PERMS_PRIVATE);
1693   }
1694 }
1695
1696 void SetFilePermissions(char *filename, int permission_class)
1697 {
1698   int running_setgid = posix_process_running_setgid();
1699   int perms = (permission_class == PERMS_PRIVATE ?
1700                FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1701
1702   if (permission_class == PERMS_PUBLIC && !running_setgid)
1703     perms = FILE_PERMS_PUBLIC_ALL;
1704
1705   chmod(filename, perms);
1706 }
1707
1708 char *getCookie(char *file_type)
1709 {
1710   static char cookie[MAX_COOKIE_LEN + 1];
1711
1712   if (strlen(program.cookie_prefix) + 1 +
1713       strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1714     return "[COOKIE ERROR]";    // should never happen
1715
1716   sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1717           program.cookie_prefix, file_type,
1718           program.version_super, program.version_major);
1719
1720   return cookie;
1721 }
1722
1723 void fprintFileHeader(FILE *file, char *basename)
1724 {
1725   char *prefix = "# ";
1726   char *sep1 = "=";
1727
1728   fprintf_line_with_prefix(file, prefix, sep1, 77);
1729   fprintf(file, "%s%s\n", prefix, basename);
1730   fprintf_line_with_prefix(file, prefix, sep1, 77);
1731   fprintf(file, "\n");
1732 }
1733
1734 int getFileVersionFromCookieString(const char *cookie)
1735 {
1736   const char *ptr_cookie1, *ptr_cookie2;
1737   const char *pattern1 = "_FILE_VERSION_";
1738   const char *pattern2 = "?.?";
1739   const int len_cookie = strlen(cookie);
1740   const int len_pattern1 = strlen(pattern1);
1741   const int len_pattern2 = strlen(pattern2);
1742   const int len_pattern = len_pattern1 + len_pattern2;
1743   int version_super, version_major;
1744
1745   if (len_cookie <= len_pattern)
1746     return -1;
1747
1748   ptr_cookie1 = &cookie[len_cookie - len_pattern];
1749   ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1750
1751   if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1752     return -1;
1753
1754   if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1755       ptr_cookie2[1] != '.' ||
1756       ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1757     return -1;
1758
1759   version_super = ptr_cookie2[0] - '0';
1760   version_major = ptr_cookie2[2] - '0';
1761
1762   return VERSION_IDENT(version_super, version_major, 0, 0);
1763 }
1764
1765 boolean checkCookieString(const char *cookie, const char *template)
1766 {
1767   const char *pattern = "_FILE_VERSION_?.?";
1768   const int len_cookie = strlen(cookie);
1769   const int len_template = strlen(template);
1770   const int len_pattern = strlen(pattern);
1771
1772   if (len_cookie != len_template)
1773     return FALSE;
1774
1775   if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1776     return FALSE;
1777
1778   return TRUE;
1779 }
1780
1781
1782 // ----------------------------------------------------------------------------
1783 // setup file list and hash handling functions
1784 // ----------------------------------------------------------------------------
1785
1786 char *getFormattedSetupEntry(char *token, char *value)
1787 {
1788   int i;
1789   static char entry[MAX_LINE_LEN];
1790
1791   // if value is an empty string, just return token without value
1792   if (*value == '\0')
1793     return token;
1794
1795   // start with the token and some spaces to format output line
1796   sprintf(entry, "%s:", token);
1797   for (i = strlen(entry); i < token_value_position; i++)
1798     strcat(entry, " ");
1799
1800   // continue with the token's value
1801   strcat(entry, value);
1802
1803   return entry;
1804 }
1805
1806 SetupFileList *newSetupFileList(char *token, char *value)
1807 {
1808   SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1809
1810   new->token = getStringCopy(token);
1811   new->value = getStringCopy(value);
1812
1813   new->next = NULL;
1814
1815   return new;
1816 }
1817
1818 void freeSetupFileList(SetupFileList *list)
1819 {
1820   if (list == NULL)
1821     return;
1822
1823   checked_free(list->token);
1824   checked_free(list->value);
1825
1826   if (list->next)
1827     freeSetupFileList(list->next);
1828
1829   free(list);
1830 }
1831
1832 char *getListEntry(SetupFileList *list, char *token)
1833 {
1834   if (list == NULL)
1835     return NULL;
1836
1837   if (strEqual(list->token, token))
1838     return list->value;
1839   else
1840     return getListEntry(list->next, token);
1841 }
1842
1843 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1844 {
1845   if (list == NULL)
1846     return NULL;
1847
1848   if (strEqual(list->token, token))
1849   {
1850     checked_free(list->value);
1851
1852     list->value = getStringCopy(value);
1853
1854     return list;
1855   }
1856   else if (list->next == NULL)
1857     return (list->next = newSetupFileList(token, value));
1858   else
1859     return setListEntry(list->next, token, value);
1860 }
1861
1862 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1863 {
1864   if (list == NULL)
1865     return NULL;
1866
1867   if (list->next == NULL)
1868     return (list->next = newSetupFileList(token, value));
1869   else
1870     return addListEntry(list->next, token, value);
1871 }
1872
1873 #if ENABLE_UNUSED_CODE
1874 #ifdef DEBUG
1875 static void printSetupFileList(SetupFileList *list)
1876 {
1877   if (!list)
1878     return;
1879
1880   Debug("setup:printSetupFileList", "token: '%s'", list->token);
1881   Debug("setup:printSetupFileList", "value: '%s'", list->value);
1882
1883   printSetupFileList(list->next);
1884 }
1885 #endif
1886 #endif
1887
1888 #ifdef DEBUG
1889 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1890 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1891 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1892 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1893 #else
1894 #define insert_hash_entry hashtable_insert
1895 #define search_hash_entry hashtable_search
1896 #define change_hash_entry hashtable_change
1897 #define remove_hash_entry hashtable_remove
1898 #endif
1899
1900 unsigned int get_hash_from_key(void *key)
1901 {
1902   /*
1903     djb2
1904
1905     This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1906     'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1907     uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1908     it works better than many other constants, prime or not) has never been
1909     adequately explained.
1910
1911     If you just want to have a good hash function, and cannot wait, djb2
1912     is one of the best string hash functions i know. It has excellent
1913     distribution and speed on many different sets of keys and table sizes.
1914     You are not likely to do better with one of the "well known" functions
1915     such as PJW, K&R, etc.
1916
1917     Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1918   */
1919
1920   char *str = (char *)key;
1921   unsigned int hash = 5381;
1922   int c;
1923
1924   while ((c = *str++))
1925     hash = ((hash << 5) + hash) + c;    // hash * 33 + c
1926
1927   return hash;
1928 }
1929
1930 static int keys_are_equal(void *key1, void *key2)
1931 {
1932   return (strEqual((char *)key1, (char *)key2));
1933 }
1934
1935 SetupFileHash *newSetupFileHash(void)
1936 {
1937   SetupFileHash *new_hash =
1938     create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1939
1940   if (new_hash == NULL)
1941     Fail("create_hashtable() failed -- out of memory");
1942
1943   return new_hash;
1944 }
1945
1946 void freeSetupFileHash(SetupFileHash *hash)
1947 {
1948   if (hash == NULL)
1949     return;
1950
1951   hashtable_destroy(hash, 1);   // 1 == also free values stored in hash
1952 }
1953
1954 char *getHashEntry(SetupFileHash *hash, char *token)
1955 {
1956   if (hash == NULL)
1957     return NULL;
1958
1959   return search_hash_entry(hash, token);
1960 }
1961
1962 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1963 {
1964   char *value_copy;
1965
1966   if (hash == NULL)
1967     return;
1968
1969   value_copy = getStringCopy(value);
1970
1971   // change value; if it does not exist, insert it as new
1972   if (!change_hash_entry(hash, token, value_copy))
1973     if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1974       Fail("cannot insert into hash -- aborting");
1975 }
1976
1977 char *removeHashEntry(SetupFileHash *hash, char *token)
1978 {
1979   if (hash == NULL)
1980     return NULL;
1981
1982   return remove_hash_entry(hash, token);
1983 }
1984
1985 #if ENABLE_UNUSED_CODE
1986 #if DEBUG
1987 static void printSetupFileHash(SetupFileHash *hash)
1988 {
1989   BEGIN_HASH_ITERATION(hash, itr)
1990   {
1991     Debug("setup:printSetupFileHash", "token: '%s'", HASH_ITERATION_TOKEN(itr));
1992     Debug("setup:printSetupFileHash", "value: '%s'", HASH_ITERATION_VALUE(itr));
1993   }
1994   END_HASH_ITERATION(hash, itr)
1995 }
1996 #endif
1997 #endif
1998
1999 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE            1
2000 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING            0
2001 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH             0
2002
2003 static boolean token_value_separator_found = FALSE;
2004 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2005 static boolean token_value_separator_warning = FALSE;
2006 #endif
2007 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2008 static boolean token_already_exists_warning = FALSE;
2009 #endif
2010
2011 static boolean getTokenValueFromSetupLineExt(char *line,
2012                                              char **token_ptr, char **value_ptr,
2013                                              char *filename, char *line_raw,
2014                                              int line_nr,
2015                                              boolean separator_required)
2016 {
2017   static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
2018   char *token, *value, *line_ptr;
2019
2020   // when externally invoked via ReadTokenValueFromLine(), copy line buffers
2021   if (line_raw == NULL)
2022   {
2023     strncpy(line_copy, line, MAX_LINE_LEN);
2024     line_copy[MAX_LINE_LEN] = '\0';
2025     line = line_copy;
2026
2027     strcpy(line_raw_copy, line_copy);
2028     line_raw = line_raw_copy;
2029   }
2030
2031   // cut trailing comment from input line
2032   for (line_ptr = line; *line_ptr; line_ptr++)
2033   {
2034     if (*line_ptr == '#')
2035     {
2036       *line_ptr = '\0';
2037       break;
2038     }
2039   }
2040
2041   // cut trailing whitespaces from input line
2042   for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2043     if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2044       *line_ptr = '\0';
2045
2046   // ignore empty lines
2047   if (*line == '\0')
2048     return FALSE;
2049
2050   // cut leading whitespaces from token
2051   for (token = line; *token; token++)
2052     if (*token != ' ' && *token != '\t')
2053       break;
2054
2055   // start with empty value as reliable default
2056   value = "";
2057
2058   token_value_separator_found = FALSE;
2059
2060   // find end of token to determine start of value
2061   for (line_ptr = token; *line_ptr; line_ptr++)
2062   {
2063     // first look for an explicit token/value separator, like ':' or '='
2064     if (*line_ptr == ':' || *line_ptr == '=')
2065     {
2066       *line_ptr = '\0';                 // terminate token string
2067       value = line_ptr + 1;             // set beginning of value
2068
2069       token_value_separator_found = TRUE;
2070
2071       break;
2072     }
2073   }
2074
2075 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2076   // fallback: if no token/value separator found, also allow whitespaces
2077   if (!token_value_separator_found && !separator_required)
2078   {
2079     for (line_ptr = token; *line_ptr; line_ptr++)
2080     {
2081       if (*line_ptr == ' ' || *line_ptr == '\t')
2082       {
2083         *line_ptr = '\0';               // terminate token string
2084         value = line_ptr + 1;           // set beginning of value
2085
2086         token_value_separator_found = TRUE;
2087
2088         break;
2089       }
2090     }
2091
2092 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2093     if (token_value_separator_found)
2094     {
2095       if (!token_value_separator_warning)
2096       {
2097         Debug("setup", "---");
2098
2099         if (filename != NULL)
2100         {
2101           Debug("setup", "missing token/value separator(s) in config file:");
2102           Debug("setup", "- config file: '%s'", filename);
2103         }
2104         else
2105         {
2106           Debug("setup", "missing token/value separator(s):");
2107         }
2108
2109         token_value_separator_warning = TRUE;
2110       }
2111
2112       if (filename != NULL)
2113         Debug("setup", "- line %d: '%s'", line_nr, line_raw);
2114       else
2115         Debug("setup", "- line: '%s'", line_raw);
2116     }
2117 #endif
2118   }
2119 #endif
2120
2121   // cut trailing whitespaces from token
2122   for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2123     if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2124       *line_ptr = '\0';
2125
2126   // cut leading whitespaces from value
2127   for (; *value; value++)
2128     if (*value != ' ' && *value != '\t')
2129       break;
2130
2131   *token_ptr = token;
2132   *value_ptr = value;
2133
2134   return TRUE;
2135 }
2136
2137 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
2138 {
2139   // while the internal (old) interface does not require a token/value
2140   // separator (for downwards compatibility with existing files which
2141   // don't use them), it is mandatory for the external (new) interface
2142
2143   return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2144 }
2145
2146 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2147                                  boolean top_recursion_level, boolean is_hash)
2148 {
2149   static SetupFileHash *include_filename_hash = NULL;
2150   char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2151   char *token, *value, *line_ptr;
2152   void *insert_ptr = NULL;
2153   boolean read_continued_line = FALSE;
2154   File *file;
2155   int line_nr = 0, token_count = 0, include_count = 0;
2156
2157 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2158   token_value_separator_warning = FALSE;
2159 #endif
2160
2161 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2162   token_already_exists_warning = FALSE;
2163 #endif
2164
2165   if (!(file = openFile(filename, MODE_READ)))
2166   {
2167 #if DEBUG_NO_CONFIG_FILE
2168     Debug("setup", "cannot open configuration file '%s'", filename);
2169 #endif
2170
2171     return FALSE;
2172   }
2173
2174   // use "insert pointer" to store list end for constant insertion complexity
2175   if (!is_hash)
2176     insert_ptr = setup_file_data;
2177
2178   // on top invocation, create hash to mark included files (to prevent loops)
2179   if (top_recursion_level)
2180     include_filename_hash = newSetupFileHash();
2181
2182   // mark this file as already included (to prevent including it again)
2183   setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2184
2185   while (!checkEndOfFile(file))
2186   {
2187     // read next line of input file
2188     if (!getStringFromFile(file, line, MAX_LINE_LEN))
2189       break;
2190
2191     // check if line was completely read and is terminated by line break
2192     if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2193       line_nr++;
2194
2195     // cut trailing line break (this can be newline and/or carriage return)
2196     for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2197       if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2198         *line_ptr = '\0';
2199
2200     // copy raw input line for later use (mainly debugging output)
2201     strcpy(line_raw, line);
2202
2203     if (read_continued_line)
2204     {
2205       // append new line to existing line, if there is enough space
2206       if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2207         strcat(previous_line, line_ptr);
2208
2209       strcpy(line, previous_line);      // copy storage buffer to line
2210
2211       read_continued_line = FALSE;
2212     }
2213
2214     // if the last character is '\', continue at next line
2215     if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2216     {
2217       line[strlen(line) - 1] = '\0';    // cut off trailing backslash
2218       strcpy(previous_line, line);      // copy line to storage buffer
2219
2220       read_continued_line = TRUE;
2221
2222       continue;
2223     }
2224
2225     if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2226                                        line_raw, line_nr, FALSE))
2227       continue;
2228
2229     if (*token)
2230     {
2231       if (strEqual(token, "include"))
2232       {
2233         if (getHashEntry(include_filename_hash, value) == NULL)
2234         {
2235           char *basepath = getBasePath(filename);
2236           char *basename = getBaseName(value);
2237           char *filename_include = getPath2(basepath, basename);
2238
2239           loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2240
2241           free(basepath);
2242           free(basename);
2243           free(filename_include);
2244
2245           include_count++;
2246         }
2247         else
2248         {
2249           Warn("ignoring already processed file '%s'", value);
2250         }
2251       }
2252       else
2253       {
2254         if (is_hash)
2255         {
2256 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2257           char *old_value =
2258             getHashEntry((SetupFileHash *)setup_file_data, token);
2259
2260           if (old_value != NULL)
2261           {
2262             if (!token_already_exists_warning)
2263             {
2264               Debug("setup", "---");
2265               Debug("setup", "duplicate token(s) found in config file:");
2266               Debug("setup", "- config file: '%s'", filename);
2267
2268               token_already_exists_warning = TRUE;
2269             }
2270
2271             Debug("setup", "- token: '%s' (in line %d)", token, line_nr);
2272             Debug("setup", "  old value: '%s'", old_value);
2273             Debug("setup", "  new value: '%s'", value);
2274           }
2275 #endif
2276
2277           setHashEntry((SetupFileHash *)setup_file_data, token, value);
2278         }
2279         else
2280         {
2281           insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2282         }
2283
2284         token_count++;
2285       }
2286     }
2287   }
2288
2289   closeFile(file);
2290
2291 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2292   if (token_value_separator_warning)
2293     Debug("setup", "---");
2294 #endif
2295
2296 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2297   if (token_already_exists_warning)
2298     Debug("setup", "---");
2299 #endif
2300
2301   if (token_count == 0 && include_count == 0)
2302     Warn("configuration file '%s' is empty", filename);
2303
2304   if (top_recursion_level)
2305     freeSetupFileHash(include_filename_hash);
2306
2307   return TRUE;
2308 }
2309
2310 static int compareSetupFileData(const void *object1, const void *object2)
2311 {
2312   const struct ConfigInfo *entry1 = (struct ConfigInfo *)object1;
2313   const struct ConfigInfo *entry2 = (struct ConfigInfo *)object2;
2314
2315   return strcmp(entry1->token, entry2->token);
2316 }
2317
2318 static void saveSetupFileHash(SetupFileHash *hash, char *filename)
2319 {
2320   int item_count = hashtable_count(hash);
2321   int item_size = sizeof(struct ConfigInfo);
2322   struct ConfigInfo *sort_array = checked_malloc(item_count * item_size);
2323   FILE *file;
2324   int i = 0;
2325
2326   // copy string pointers from hash to array
2327   BEGIN_HASH_ITERATION(hash, itr)
2328   {
2329     sort_array[i].token = HASH_ITERATION_TOKEN(itr);
2330     sort_array[i].value = HASH_ITERATION_VALUE(itr);
2331
2332     i++;
2333
2334     if (i > item_count)         // should never happen
2335       break;
2336   }
2337   END_HASH_ITERATION(hash, itr)
2338
2339   // sort string pointers from hash in array
2340   qsort(sort_array, item_count, item_size, compareSetupFileData);
2341
2342   if (!(file = fopen(filename, MODE_WRITE)))
2343   {
2344     Warn("cannot write configuration file '%s'", filename);
2345
2346     return;
2347   }
2348
2349   fprintf(file, "%s\n\n", getFormattedSetupEntry("program.version",
2350                                                  program.version_string));
2351   for (i = 0; i < item_count; i++)
2352     fprintf(file, "%s\n", getFormattedSetupEntry(sort_array[i].token,
2353                                                  sort_array[i].value));
2354   fclose(file);
2355
2356   checked_free(sort_array);
2357 }
2358
2359 SetupFileList *loadSetupFileList(char *filename)
2360 {
2361   SetupFileList *setup_file_list = newSetupFileList("", "");
2362   SetupFileList *first_valid_list_entry;
2363
2364   if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2365   {
2366     freeSetupFileList(setup_file_list);
2367
2368     return NULL;
2369   }
2370
2371   first_valid_list_entry = setup_file_list->next;
2372
2373   // free empty list header
2374   setup_file_list->next = NULL;
2375   freeSetupFileList(setup_file_list);
2376
2377   return first_valid_list_entry;
2378 }
2379
2380 SetupFileHash *loadSetupFileHash(char *filename)
2381 {
2382   SetupFileHash *setup_file_hash = newSetupFileHash();
2383
2384   if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2385   {
2386     freeSetupFileHash(setup_file_hash);
2387
2388     return NULL;
2389   }
2390
2391   return setup_file_hash;
2392 }
2393
2394
2395 // ============================================================================
2396 // setup file stuff
2397 // ============================================================================
2398
2399 #define TOKEN_STR_LAST_LEVEL_SERIES             "last_level_series"
2400 #define TOKEN_STR_LAST_PLAYED_LEVEL             "last_played_level"
2401 #define TOKEN_STR_HANDICAP_LEVEL                "handicap_level"
2402 #define TOKEN_STR_LAST_USER                     "last_user"
2403
2404 // level directory info
2405 #define LEVELINFO_TOKEN_IDENTIFIER              0
2406 #define LEVELINFO_TOKEN_NAME                    1
2407 #define LEVELINFO_TOKEN_NAME_SORTING            2
2408 #define LEVELINFO_TOKEN_AUTHOR                  3
2409 #define LEVELINFO_TOKEN_YEAR                    4
2410 #define LEVELINFO_TOKEN_PROGRAM_TITLE           5
2411 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT       6
2412 #define LEVELINFO_TOKEN_PROGRAM_COMPANY         7
2413 #define LEVELINFO_TOKEN_IMPORTED_FROM           8
2414 #define LEVELINFO_TOKEN_IMPORTED_BY             9
2415 #define LEVELINFO_TOKEN_TESTED_BY               10
2416 #define LEVELINFO_TOKEN_LEVELS                  11
2417 #define LEVELINFO_TOKEN_FIRST_LEVEL             12
2418 #define LEVELINFO_TOKEN_SORT_PRIORITY           13
2419 #define LEVELINFO_TOKEN_LATEST_ENGINE           14
2420 #define LEVELINFO_TOKEN_LEVEL_GROUP             15
2421 #define LEVELINFO_TOKEN_READONLY                16
2422 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS        17
2423 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA        18
2424 #define LEVELINFO_TOKEN_GRAPHICS_SET            19
2425 #define LEVELINFO_TOKEN_SOUNDS_SET_DEFAULT      20
2426 #define LEVELINFO_TOKEN_SOUNDS_SET_LOWPASS      21
2427 #define LEVELINFO_TOKEN_SOUNDS_SET              22
2428 #define LEVELINFO_TOKEN_MUSIC_SET               23
2429 #define LEVELINFO_TOKEN_FILENAME                24
2430 #define LEVELINFO_TOKEN_FILETYPE                25
2431 #define LEVELINFO_TOKEN_SPECIAL_FLAGS           26
2432 #define LEVELINFO_TOKEN_HANDICAP                27
2433 #define LEVELINFO_TOKEN_SKIP_LEVELS             28
2434 #define LEVELINFO_TOKEN_USE_EMC_TILES           29
2435
2436 #define NUM_LEVELINFO_TOKENS                    30
2437
2438 static LevelDirTree ldi;
2439
2440 static struct TokenInfo levelinfo_tokens[] =
2441 {
2442   // level directory info
2443   { TYPE_STRING,        &ldi.identifier,        "identifier"            },
2444   { TYPE_STRING,        &ldi.name,              "name"                  },
2445   { TYPE_STRING,        &ldi.name_sorting,      "name_sorting"          },
2446   { TYPE_STRING,        &ldi.author,            "author"                },
2447   { TYPE_STRING,        &ldi.year,              "year"                  },
2448   { TYPE_STRING,        &ldi.program_title,     "program_title"         },
2449   { TYPE_STRING,        &ldi.program_copyright, "program_copyright"     },
2450   { TYPE_STRING,        &ldi.program_company,   "program_company"       },
2451   { TYPE_STRING,        &ldi.imported_from,     "imported_from"         },
2452   { TYPE_STRING,        &ldi.imported_by,       "imported_by"           },
2453   { TYPE_STRING,        &ldi.tested_by,         "tested_by"             },
2454   { TYPE_INTEGER,       &ldi.levels,            "levels"                },
2455   { TYPE_INTEGER,       &ldi.first_level,       "first_level"           },
2456   { TYPE_INTEGER,       &ldi.sort_priority,     "sort_priority"         },
2457   { TYPE_BOOLEAN,       &ldi.latest_engine,     "latest_engine"         },
2458   { TYPE_BOOLEAN,       &ldi.level_group,       "level_group"           },
2459   { TYPE_BOOLEAN,       &ldi.readonly,          "readonly"              },
2460   { TYPE_STRING,        &ldi.graphics_set_ecs,  "graphics_set.ecs"      },
2461   { TYPE_STRING,        &ldi.graphics_set_aga,  "graphics_set.aga"      },
2462   { TYPE_STRING,        &ldi.graphics_set,      "graphics_set"          },
2463   { TYPE_STRING,        &ldi.sounds_set_default,"sounds_set.default"    },
2464   { TYPE_STRING,        &ldi.sounds_set_lowpass,"sounds_set.lowpass"    },
2465   { TYPE_STRING,        &ldi.sounds_set,        "sounds_set"            },
2466   { TYPE_STRING,        &ldi.music_set,         "music_set"             },
2467   { TYPE_STRING,        &ldi.level_filename,    "filename"              },
2468   { TYPE_STRING,        &ldi.level_filetype,    "filetype"              },
2469   { TYPE_STRING,        &ldi.special_flags,     "special_flags"         },
2470   { TYPE_BOOLEAN,       &ldi.handicap,          "handicap"              },
2471   { TYPE_BOOLEAN,       &ldi.skip_levels,       "skip_levels"           },
2472   { TYPE_BOOLEAN,       &ldi.use_emc_tiles,     "use_emc_tiles"         }
2473 };
2474
2475 static struct TokenInfo artworkinfo_tokens[] =
2476 {
2477   // artwork directory info
2478   { TYPE_STRING,        &ldi.identifier,        "identifier"            },
2479   { TYPE_STRING,        &ldi.subdir,            "subdir"                },
2480   { TYPE_STRING,        &ldi.name,              "name"                  },
2481   { TYPE_STRING,        &ldi.name_sorting,      "name_sorting"          },
2482   { TYPE_STRING,        &ldi.author,            "author"                },
2483   { TYPE_STRING,        &ldi.program_title,     "program_title"         },
2484   { TYPE_STRING,        &ldi.program_copyright, "program_copyright"     },
2485   { TYPE_STRING,        &ldi.program_company,   "program_company"       },
2486   { TYPE_INTEGER,       &ldi.sort_priority,     "sort_priority"         },
2487   { TYPE_STRING,        &ldi.basepath,          "basepath"              },
2488   { TYPE_STRING,        &ldi.fullpath,          "fullpath"              },
2489   { TYPE_BOOLEAN,       &ldi.in_user_dir,       "in_user_dir"           },
2490   { TYPE_STRING,        &ldi.class_desc,        "class_desc"            },
2491
2492   { -1,                 NULL,                   NULL                    },
2493 };
2494
2495 static char *optional_tokens[] =
2496 {
2497   "program_title",
2498   "program_copyright",
2499   "program_company",
2500
2501   NULL
2502 };
2503
2504 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2505 {
2506   ti->type = type;
2507
2508   ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR    ? &leveldir_first :
2509                   ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2510                   ti->type == TREE_TYPE_SOUNDS_DIR   ? &artwork.snd_first :
2511                   ti->type == TREE_TYPE_MUSIC_DIR    ? &artwork.mus_first :
2512                   NULL);
2513
2514   ti->node_parent = NULL;
2515   ti->node_group = NULL;
2516   ti->next = NULL;
2517
2518   ti->cl_first = -1;
2519   ti->cl_cursor = -1;
2520
2521   ti->subdir = NULL;
2522   ti->fullpath = NULL;
2523   ti->basepath = NULL;
2524   ti->identifier = NULL;
2525   ti->name = getStringCopy(ANONYMOUS_NAME);
2526   ti->name_sorting = NULL;
2527   ti->author = getStringCopy(ANONYMOUS_NAME);
2528   ti->year = NULL;
2529
2530   ti->program_title = NULL;
2531   ti->program_copyright = NULL;
2532   ti->program_company = NULL;
2533
2534   ti->sort_priority = LEVELCLASS_UNDEFINED;     // default: least priority
2535   ti->latest_engine = FALSE;                    // default: get from level
2536   ti->parent_link = FALSE;
2537   ti->is_copy = FALSE;
2538   ti->in_user_dir = FALSE;
2539   ti->user_defined = FALSE;
2540   ti->color = 0;
2541   ti->class_desc = NULL;
2542
2543   ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2544
2545   if (ti->type == TREE_TYPE_LEVEL_DIR)
2546   {
2547     ti->imported_from = NULL;
2548     ti->imported_by = NULL;
2549     ti->tested_by = NULL;
2550
2551     ti->graphics_set_ecs = NULL;
2552     ti->graphics_set_aga = NULL;
2553     ti->graphics_set = NULL;
2554     ti->sounds_set_default = NULL;
2555     ti->sounds_set_lowpass = NULL;
2556     ti->sounds_set = NULL;
2557     ti->music_set = NULL;
2558     ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2559     ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2560     ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2561
2562     ti->level_filename = NULL;
2563     ti->level_filetype = NULL;
2564
2565     ti->special_flags = NULL;
2566
2567     ti->levels = 0;
2568     ti->first_level = 0;
2569     ti->last_level = 0;
2570     ti->level_group = FALSE;
2571     ti->handicap_level = 0;
2572     ti->readonly = TRUE;
2573     ti->handicap = TRUE;
2574     ti->skip_levels = FALSE;
2575
2576     ti->use_emc_tiles = FALSE;
2577   }
2578 }
2579
2580 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2581 {
2582   if (parent == NULL)
2583   {
2584     Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
2585
2586     setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2587
2588     return;
2589   }
2590
2591   // copy all values from the parent structure
2592
2593   ti->type = parent->type;
2594
2595   ti->node_top = parent->node_top;
2596   ti->node_parent = parent;
2597   ti->node_group = NULL;
2598   ti->next = NULL;
2599
2600   ti->cl_first = -1;
2601   ti->cl_cursor = -1;
2602
2603   ti->subdir = NULL;
2604   ti->fullpath = NULL;
2605   ti->basepath = NULL;
2606   ti->identifier = NULL;
2607   ti->name = getStringCopy(ANONYMOUS_NAME);
2608   ti->name_sorting = NULL;
2609   ti->author = getStringCopy(parent->author);
2610   ti->year = getStringCopy(parent->year);
2611
2612   ti->program_title = getStringCopy(parent->program_title);
2613   ti->program_copyright = getStringCopy(parent->program_copyright);
2614   ti->program_company = getStringCopy(parent->program_company);
2615
2616   ti->sort_priority = parent->sort_priority;
2617   ti->latest_engine = parent->latest_engine;
2618   ti->parent_link = FALSE;
2619   ti->is_copy = FALSE;
2620   ti->in_user_dir = parent->in_user_dir;
2621   ti->user_defined = parent->user_defined;
2622   ti->color = parent->color;
2623   ti->class_desc = getStringCopy(parent->class_desc);
2624
2625   ti->infotext = getStringCopy(parent->infotext);
2626
2627   if (ti->type == TREE_TYPE_LEVEL_DIR)
2628   {
2629     ti->imported_from = getStringCopy(parent->imported_from);
2630     ti->imported_by = getStringCopy(parent->imported_by);
2631     ti->tested_by = getStringCopy(parent->tested_by);
2632
2633     ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2634     ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2635     ti->graphics_set = getStringCopy(parent->graphics_set);
2636     ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
2637     ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
2638     ti->sounds_set = getStringCopy(parent->sounds_set);
2639     ti->music_set = getStringCopy(parent->music_set);
2640     ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2641     ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2642     ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2643
2644     ti->level_filename = getStringCopy(parent->level_filename);
2645     ti->level_filetype = getStringCopy(parent->level_filetype);
2646
2647     ti->special_flags = getStringCopy(parent->special_flags);
2648
2649     ti->levels = parent->levels;
2650     ti->first_level = parent->first_level;
2651     ti->last_level = parent->last_level;
2652     ti->level_group = FALSE;
2653     ti->handicap_level = parent->handicap_level;
2654     ti->readonly = parent->readonly;
2655     ti->handicap = parent->handicap;
2656     ti->skip_levels = parent->skip_levels;
2657
2658     ti->use_emc_tiles = parent->use_emc_tiles;
2659   }
2660 }
2661
2662 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2663 {
2664   TreeInfo *ti_copy = newTreeInfo();
2665
2666   // copy all values from the original structure
2667
2668   ti_copy->type                 = ti->type;
2669
2670   ti_copy->node_top             = ti->node_top;
2671   ti_copy->node_parent          = ti->node_parent;
2672   ti_copy->node_group           = ti->node_group;
2673   ti_copy->next                 = ti->next;
2674
2675   ti_copy->cl_first             = ti->cl_first;
2676   ti_copy->cl_cursor            = ti->cl_cursor;
2677
2678   ti_copy->subdir               = getStringCopy(ti->subdir);
2679   ti_copy->fullpath             = getStringCopy(ti->fullpath);
2680   ti_copy->basepath             = getStringCopy(ti->basepath);
2681   ti_copy->identifier           = getStringCopy(ti->identifier);
2682   ti_copy->name                 = getStringCopy(ti->name);
2683   ti_copy->name_sorting         = getStringCopy(ti->name_sorting);
2684   ti_copy->author               = getStringCopy(ti->author);
2685   ti_copy->year                 = getStringCopy(ti->year);
2686
2687   ti_copy->program_title        = getStringCopy(ti->program_title);
2688   ti_copy->program_copyright    = getStringCopy(ti->program_copyright);
2689   ti_copy->program_company      = getStringCopy(ti->program_company);
2690
2691   ti_copy->imported_from        = getStringCopy(ti->imported_from);
2692   ti_copy->imported_by          = getStringCopy(ti->imported_by);
2693   ti_copy->tested_by            = getStringCopy(ti->tested_by);
2694
2695   ti_copy->graphics_set_ecs     = getStringCopy(ti->graphics_set_ecs);
2696   ti_copy->graphics_set_aga     = getStringCopy(ti->graphics_set_aga);
2697   ti_copy->graphics_set         = getStringCopy(ti->graphics_set);
2698   ti_copy->sounds_set_default   = getStringCopy(ti->sounds_set_default);
2699   ti_copy->sounds_set_lowpass   = getStringCopy(ti->sounds_set_lowpass);
2700   ti_copy->sounds_set           = getStringCopy(ti->sounds_set);
2701   ti_copy->music_set            = getStringCopy(ti->music_set);
2702   ti_copy->graphics_path        = getStringCopy(ti->graphics_path);
2703   ti_copy->sounds_path          = getStringCopy(ti->sounds_path);
2704   ti_copy->music_path           = getStringCopy(ti->music_path);
2705
2706   ti_copy->level_filename       = getStringCopy(ti->level_filename);
2707   ti_copy->level_filetype       = getStringCopy(ti->level_filetype);
2708
2709   ti_copy->special_flags        = getStringCopy(ti->special_flags);
2710
2711   ti_copy->levels               = ti->levels;
2712   ti_copy->first_level          = ti->first_level;
2713   ti_copy->last_level           = ti->last_level;
2714   ti_copy->sort_priority        = ti->sort_priority;
2715
2716   ti_copy->latest_engine        = ti->latest_engine;
2717
2718   ti_copy->level_group          = ti->level_group;
2719   ti_copy->parent_link          = ti->parent_link;
2720   ti_copy->is_copy              = ti->is_copy;
2721   ti_copy->in_user_dir          = ti->in_user_dir;
2722   ti_copy->user_defined         = ti->user_defined;
2723   ti_copy->readonly             = ti->readonly;
2724   ti_copy->handicap             = ti->handicap;
2725   ti_copy->skip_levels          = ti->skip_levels;
2726
2727   ti_copy->use_emc_tiles        = ti->use_emc_tiles;
2728
2729   ti_copy->color                = ti->color;
2730   ti_copy->class_desc           = getStringCopy(ti->class_desc);
2731   ti_copy->handicap_level       = ti->handicap_level;
2732
2733   ti_copy->infotext             = getStringCopy(ti->infotext);
2734
2735   return ti_copy;
2736 }
2737
2738 void freeTreeInfo(TreeInfo *ti)
2739 {
2740   if (ti == NULL)
2741     return;
2742
2743   checked_free(ti->subdir);
2744   checked_free(ti->fullpath);
2745   checked_free(ti->basepath);
2746   checked_free(ti->identifier);
2747
2748   checked_free(ti->name);
2749   checked_free(ti->name_sorting);
2750   checked_free(ti->author);
2751   checked_free(ti->year);
2752
2753   checked_free(ti->program_title);
2754   checked_free(ti->program_copyright);
2755   checked_free(ti->program_company);
2756
2757   checked_free(ti->class_desc);
2758
2759   checked_free(ti->infotext);
2760
2761   if (ti->type == TREE_TYPE_LEVEL_DIR)
2762   {
2763     checked_free(ti->imported_from);
2764     checked_free(ti->imported_by);
2765     checked_free(ti->tested_by);
2766
2767     checked_free(ti->graphics_set_ecs);
2768     checked_free(ti->graphics_set_aga);
2769     checked_free(ti->graphics_set);
2770     checked_free(ti->sounds_set_default);
2771     checked_free(ti->sounds_set_lowpass);
2772     checked_free(ti->sounds_set);
2773     checked_free(ti->music_set);
2774
2775     checked_free(ti->graphics_path);
2776     checked_free(ti->sounds_path);
2777     checked_free(ti->music_path);
2778
2779     checked_free(ti->level_filename);
2780     checked_free(ti->level_filetype);
2781
2782     checked_free(ti->special_flags);
2783   }
2784
2785   // recursively free child node
2786   if (ti->node_group)
2787     freeTreeInfo(ti->node_group);
2788
2789   // recursively free next node
2790   if (ti->next)
2791     freeTreeInfo(ti->next);
2792
2793   checked_free(ti);
2794 }
2795
2796 void setSetupInfo(struct TokenInfo *token_info,
2797                   int token_nr, char *token_value)
2798 {
2799   int token_type = token_info[token_nr].type;
2800   void *setup_value = token_info[token_nr].value;
2801
2802   if (token_value == NULL)
2803     return;
2804
2805   // set setup field to corresponding token value
2806   switch (token_type)
2807   {
2808     case TYPE_BOOLEAN:
2809     case TYPE_SWITCH:
2810       *(boolean *)setup_value = get_boolean_from_string(token_value);
2811       break;
2812
2813     case TYPE_SWITCH3:
2814       *(int *)setup_value = get_switch3_from_string(token_value);
2815       break;
2816
2817     case TYPE_KEY:
2818       *(Key *)setup_value = getKeyFromKeyName(token_value);
2819       break;
2820
2821     case TYPE_KEY_X11:
2822       *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2823       break;
2824
2825     case TYPE_INTEGER:
2826       *(int *)setup_value = get_integer_from_string(token_value);
2827       break;
2828
2829     case TYPE_STRING:
2830       checked_free(*(char **)setup_value);
2831       *(char **)setup_value = getStringCopy(token_value);
2832       break;
2833
2834     case TYPE_PLAYER:
2835       *(int *)setup_value = get_player_nr_from_string(token_value);
2836       break;
2837
2838     default:
2839       break;
2840   }
2841 }
2842
2843 static int compareTreeInfoEntries(const void *object1, const void *object2)
2844 {
2845   const TreeInfo *entry1 = *((TreeInfo **)object1);
2846   const TreeInfo *entry2 = *((TreeInfo **)object2);
2847   int tree_sorting1 = TREE_SORTING(entry1);
2848   int tree_sorting2 = TREE_SORTING(entry2);
2849
2850   if (tree_sorting1 != tree_sorting2)
2851     return (tree_sorting1 - tree_sorting2);
2852   else
2853     return strcasecmp(entry1->name_sorting, entry2->name_sorting);
2854 }
2855
2856 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2857 {
2858   TreeInfo *ti_new;
2859
2860   if (node_parent == NULL)
2861     return NULL;
2862
2863   ti_new = newTreeInfo();
2864   setTreeInfoToDefaults(ti_new, node_parent->type);
2865
2866   ti_new->node_parent = node_parent;
2867   ti_new->parent_link = TRUE;
2868
2869   setString(&ti_new->identifier, node_parent->identifier);
2870   setString(&ti_new->name, BACKLINK_TEXT_PARENT);
2871   setString(&ti_new->name_sorting, ti_new->name);
2872
2873   setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2874   setString(&ti_new->fullpath, node_parent->fullpath);
2875
2876   ti_new->sort_priority = LEVELCLASS_PARENT;
2877   ti_new->latest_engine = node_parent->latest_engine;
2878
2879   setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2880
2881   pushTreeInfo(&node_parent->node_group, ti_new);
2882
2883   return ti_new;
2884 }
2885
2886 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
2887 {
2888   if (node_first == NULL)
2889     return NULL;
2890
2891   TreeInfo *ti_new = newTreeInfo();
2892   int type = node_first->type;
2893
2894   setTreeInfoToDefaults(ti_new, type);
2895
2896   ti_new->node_parent = NULL;
2897   ti_new->parent_link = FALSE;
2898
2899   setString(&ti_new->identifier, "top_tree_node");
2900   setString(&ti_new->name, TREE_INFOTEXT(type));
2901   setString(&ti_new->name_sorting, ti_new->name);
2902
2903   setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
2904   setString(&ti_new->fullpath, ".");
2905
2906   ti_new->sort_priority = LEVELCLASS_TOP;
2907   ti_new->latest_engine = node_first->latest_engine;
2908
2909   setString(&ti_new->class_desc, TREE_INFOTEXT(type));
2910
2911   ti_new->node_group = node_first;
2912   ti_new->level_group = TRUE;
2913
2914   TreeInfo *ti_new2 = createParentTreeInfoNode(ti_new);
2915
2916   setString(&ti_new2->name, TREE_BACKLINK_TEXT(type));
2917   setString(&ti_new2->name_sorting, ti_new2->name);
2918
2919   return ti_new;
2920 }
2921
2922 static void setTreeInfoParentNodes(TreeInfo *node, TreeInfo *node_parent)
2923 {
2924   while (node)
2925   {
2926     if (node->node_group)
2927       setTreeInfoParentNodes(node->node_group, node);
2928
2929     node->node_parent = node_parent;
2930
2931     node = node->next;
2932   }
2933 }
2934
2935
2936 // ----------------------------------------------------------------------------
2937 // functions for handling level and custom artwork info cache
2938 // ----------------------------------------------------------------------------
2939
2940 static void LoadArtworkInfoCache(void)
2941 {
2942   InitCacheDirectory();
2943
2944   if (artworkinfo_cache_old == NULL)
2945   {
2946     char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2947
2948     // try to load artwork info hash from already existing cache file
2949     artworkinfo_cache_old = loadSetupFileHash(filename);
2950
2951     // try to get program version that artwork info cache was written with
2952     char *version = getHashEntry(artworkinfo_cache_old, "program.version");
2953
2954     // check program version of artwork info cache against current version
2955     if (!strEqual(version, program.version_string))
2956     {
2957       freeSetupFileHash(artworkinfo_cache_old);
2958
2959       artworkinfo_cache_old = NULL;
2960     }
2961
2962     // if no artwork info cache file was found, start with empty hash
2963     if (artworkinfo_cache_old == NULL)
2964       artworkinfo_cache_old = newSetupFileHash();
2965
2966     free(filename);
2967   }
2968
2969   if (artworkinfo_cache_new == NULL)
2970     artworkinfo_cache_new = newSetupFileHash();
2971
2972   update_artworkinfo_cache = FALSE;
2973 }
2974
2975 static void SaveArtworkInfoCache(void)
2976 {
2977   if (!update_artworkinfo_cache)
2978     return;
2979
2980   char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2981
2982   InitCacheDirectory();
2983
2984   saveSetupFileHash(artworkinfo_cache_new, filename);
2985
2986   free(filename);
2987 }
2988
2989 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2990 {
2991   static char *prefix = NULL;
2992
2993   checked_free(prefix);
2994
2995   prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2996
2997   return prefix;
2998 }
2999
3000 // (identical to above function, but separate string buffer needed -- nasty)
3001 static char *getCacheToken(char *prefix, char *suffix)
3002 {
3003   static char *token = NULL;
3004
3005   checked_free(token);
3006
3007   token = getStringCat2WithSeparator(prefix, suffix, ".");
3008
3009   return token;
3010 }
3011
3012 static char *getFileTimestampString(char *filename)
3013 {
3014   return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
3015 }
3016
3017 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
3018 {
3019   struct stat file_status;
3020
3021   if (timestamp_string == NULL)
3022     return TRUE;
3023
3024   if (!fileExists(filename))                    // file does not exist
3025     return (atoi(timestamp_string) != 0);
3026
3027   if (stat(filename, &file_status) != 0)        // cannot stat file
3028     return TRUE;
3029
3030   return (file_status.st_mtime != atoi(timestamp_string));
3031 }
3032
3033 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3034 {
3035   char *identifier = level_node->subdir;
3036   char *type_string = ARTWORK_DIRECTORY(type);
3037   char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3038   char *token_main = getCacheToken(token_prefix, "CACHED");
3039   char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3040   boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3041   TreeInfo *artwork_info = NULL;
3042
3043   if (!use_artworkinfo_cache)
3044     return NULL;
3045
3046   if (optional_tokens_hash == NULL)
3047   {
3048     int i;
3049
3050     // create hash from list of optional tokens (for quick access)
3051     optional_tokens_hash = newSetupFileHash();
3052     for (i = 0; optional_tokens[i] != NULL; i++)
3053       setHashEntry(optional_tokens_hash, optional_tokens[i], "");
3054   }
3055
3056   if (cached)
3057   {
3058     int i;
3059
3060     artwork_info = newTreeInfo();
3061     setTreeInfoToDefaults(artwork_info, type);
3062
3063     // set all structure fields according to the token/value pairs
3064     ldi = *artwork_info;
3065     for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3066     {
3067       char *token_suffix = artworkinfo_tokens[i].text;
3068       char *token = getCacheToken(token_prefix, token_suffix);
3069       char *value = getHashEntry(artworkinfo_cache_old, token);
3070       boolean optional =
3071         (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
3072
3073       setSetupInfo(artworkinfo_tokens, i, value);
3074
3075       // check if cache entry for this item is mandatory, but missing
3076       if (value == NULL && !optional)
3077       {
3078         Warn("missing cache entry '%s'", token);
3079
3080         cached = FALSE;
3081       }
3082     }
3083
3084     *artwork_info = ldi;
3085   }
3086
3087   if (cached)
3088   {
3089     char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3090                                         LEVELINFO_FILENAME);
3091     char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3092                                           ARTWORKINFO_FILENAME(type));
3093
3094     // check if corresponding "levelinfo.conf" file has changed
3095     token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3096     cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3097
3098     if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3099       cached = FALSE;
3100
3101     // check if corresponding "<artworkinfo>.conf" file has changed
3102     token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3103     cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3104
3105     if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3106       cached = FALSE;
3107
3108     checked_free(filename_levelinfo);
3109     checked_free(filename_artworkinfo);
3110   }
3111
3112   if (!cached && artwork_info != NULL)
3113   {
3114     freeTreeInfo(artwork_info);
3115
3116     return NULL;
3117   }
3118
3119   return artwork_info;
3120 }
3121
3122 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3123                                      LevelDirTree *level_node, int type)
3124 {
3125   char *identifier = level_node->subdir;
3126   char *type_string = ARTWORK_DIRECTORY(type);
3127   char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3128   char *token_main = getCacheToken(token_prefix, "CACHED");
3129   boolean set_cache_timestamps = TRUE;
3130   int i;
3131
3132   setHashEntry(artworkinfo_cache_new, token_main, "true");
3133
3134   if (set_cache_timestamps)
3135   {
3136     char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3137                                         LEVELINFO_FILENAME);
3138     char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3139                                           ARTWORKINFO_FILENAME(type));
3140     char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3141     char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3142
3143     token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3144     setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3145
3146     token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3147     setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3148
3149     checked_free(filename_levelinfo);
3150     checked_free(filename_artworkinfo);
3151     checked_free(timestamp_levelinfo);
3152     checked_free(timestamp_artworkinfo);
3153   }
3154
3155   ldi = *artwork_info;
3156   for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3157   {
3158     char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3159     char *value = getSetupValue(artworkinfo_tokens[i].type,
3160                                 artworkinfo_tokens[i].value);
3161     if (value != NULL)
3162       setHashEntry(artworkinfo_cache_new, token, value);
3163   }
3164 }
3165
3166
3167 // ----------------------------------------------------------------------------
3168 // functions for loading level info and custom artwork info
3169 // ----------------------------------------------------------------------------
3170
3171 int GetZipFileTreeType(char *zip_filename)
3172 {
3173   static char *top_dir_path = NULL;
3174   static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3175   static char *conf_basename[NUM_BASE_TREE_TYPES] =
3176   {
3177     GRAPHICSINFO_FILENAME,
3178     SOUNDSINFO_FILENAME,
3179     MUSICINFO_FILENAME,
3180     LEVELINFO_FILENAME
3181   };
3182   int j;
3183
3184   checked_free(top_dir_path);
3185   top_dir_path = NULL;
3186
3187   for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3188   {
3189     checked_free(top_dir_conf_filename[j]);
3190     top_dir_conf_filename[j] = NULL;
3191   }
3192
3193   char **zip_entries = zip_list(zip_filename);
3194
3195   // check if zip file successfully opened
3196   if (zip_entries == NULL || zip_entries[0] == NULL)
3197     return TREE_TYPE_UNDEFINED;
3198
3199   // first zip file entry is expected to be top level directory
3200   char *top_dir = zip_entries[0];
3201
3202   // check if valid top level directory found in zip file
3203   if (!strSuffix(top_dir, "/"))
3204     return TREE_TYPE_UNDEFINED;
3205
3206   // get filenames of valid configuration files in top level directory
3207   for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3208     top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3209
3210   int tree_type = TREE_TYPE_UNDEFINED;
3211   int e = 0;
3212
3213   while (zip_entries[e] != NULL)
3214   {
3215     // check if every zip file entry is below top level directory
3216     if (!strPrefix(zip_entries[e], top_dir))
3217       return TREE_TYPE_UNDEFINED;
3218
3219     // check if this zip file entry is a valid configuration filename
3220     for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3221     {
3222       if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3223       {
3224         // only exactly one valid configuration file allowed
3225         if (tree_type != TREE_TYPE_UNDEFINED)
3226           return TREE_TYPE_UNDEFINED;
3227
3228         tree_type = j;
3229       }
3230     }
3231
3232     e++;
3233   }
3234
3235   return tree_type;
3236 }
3237
3238 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3239                                         int tree_type)
3240 {
3241   static char *top_dir_path = NULL;
3242   static char *top_dir_conf_filename = NULL;
3243
3244   checked_free(top_dir_path);
3245   checked_free(top_dir_conf_filename);
3246
3247   top_dir_path = NULL;
3248   top_dir_conf_filename = NULL;
3249
3250   char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3251                          ARTWORKINFO_FILENAME(tree_type));
3252
3253   // check if valid configuration filename determined
3254   if (conf_basename == NULL || strEqual(conf_basename, ""))
3255     return FALSE;
3256
3257   char **zip_entries = zip_list(zip_filename);
3258
3259   // check if zip file successfully opened
3260   if (zip_entries == NULL || zip_entries[0] == NULL)
3261     return FALSE;
3262
3263   // first zip file entry is expected to be top level directory
3264   char *top_dir = zip_entries[0];
3265
3266   // check if valid top level directory found in zip file
3267   if (!strSuffix(top_dir, "/"))
3268     return FALSE;
3269
3270   // get path of extracted top level directory
3271   top_dir_path = getPath2(directory, top_dir);
3272
3273   // remove trailing directory separator from top level directory path
3274   // (required to be able to check for file and directory in next step)
3275   top_dir_path[strlen(top_dir_path) - 1] = '\0';
3276
3277   // check if zip file's top level directory already exists in target directory
3278   if (fileExists(top_dir_path))         // (checks for file and directory)
3279     return FALSE;
3280
3281   // get filename of configuration file in top level directory
3282   top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3283
3284   boolean found_top_dir_conf_filename = FALSE;
3285   int i = 0;
3286
3287   while (zip_entries[i] != NULL)
3288   {
3289     // check if every zip file entry is below top level directory
3290     if (!strPrefix(zip_entries[i], top_dir))
3291       return FALSE;
3292
3293     // check if this zip file entry is the configuration filename
3294     if (strEqual(zip_entries[i], top_dir_conf_filename))
3295       found_top_dir_conf_filename = TRUE;
3296
3297     i++;
3298   }
3299
3300   // check if valid configuration filename was found in zip file
3301   if (!found_top_dir_conf_filename)
3302     return FALSE;
3303
3304   return TRUE;
3305 }
3306
3307 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3308                                   int tree_type)
3309 {
3310   boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3311                                                     tree_type);
3312
3313   if (!zip_file_valid)
3314   {
3315     Warn("zip file '%s' rejected!", zip_filename);
3316
3317     return NULL;
3318   }
3319
3320   char **zip_entries = zip_extract(zip_filename, directory);
3321
3322   if (zip_entries == NULL)
3323   {
3324     Warn("zip file '%s' could not be extracted!", zip_filename);
3325
3326     return NULL;
3327   }
3328
3329   Info("zip file '%s' successfully extracted!", zip_filename);
3330
3331   // first zip file entry contains top level directory
3332   char *top_dir = zip_entries[0];
3333
3334   // remove trailing directory separator from top level directory
3335   top_dir[strlen(top_dir) - 1] = '\0';
3336
3337   return top_dir;
3338 }
3339
3340 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3341 {
3342   Directory *dir;
3343   DirectoryEntry *dir_entry;
3344
3345   if ((dir = openDirectory(directory)) == NULL)
3346   {
3347     // display error if directory is main "options.graphics_directory" etc.
3348     if (tree_type == TREE_TYPE_LEVEL_DIR ||
3349         directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3350       Warn("cannot read directory '%s'", directory);
3351
3352     return;
3353   }
3354
3355   while ((dir_entry = readDirectory(dir)) != NULL)      // loop all entries
3356   {
3357     // skip non-zip files (and also directories with zip extension)
3358     if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3359       continue;
3360
3361     char *zip_filename = getPath2(directory, dir_entry->basename);
3362     char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3363     char *zip_filename_rejected  = getStringCat2(zip_filename, ".rejected");
3364
3365     // check if zip file hasn't already been extracted or rejected
3366     if (!fileExists(zip_filename_extracted) &&
3367         !fileExists(zip_filename_rejected))
3368     {
3369       char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3370                                                   tree_type);
3371       char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3372                                zip_filename_rejected);
3373       FILE *marker_file;
3374
3375       // create empty file to mark zip file as extracted or rejected
3376       if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3377         fclose(marker_file);
3378
3379       free(zip_filename);
3380       free(zip_filename_extracted);
3381       free(zip_filename_rejected);
3382     }
3383   }
3384
3385   closeDirectory(dir);
3386 }
3387
3388 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3389 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3390
3391 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3392                                           TreeInfo *node_parent,
3393                                           char *level_directory,
3394                                           char *directory_name)
3395 {
3396   char *directory_path = getPath2(level_directory, directory_name);
3397   char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3398   SetupFileHash *setup_file_hash;
3399   LevelDirTree *leveldir_new = NULL;
3400   int i;
3401
3402   // unless debugging, silently ignore directories without "levelinfo.conf"
3403   if (!options.debug && !fileExists(filename))
3404   {
3405     free(directory_path);
3406     free(filename);
3407
3408     return FALSE;
3409   }
3410
3411   setup_file_hash = loadSetupFileHash(filename);
3412
3413   if (setup_file_hash == NULL)
3414   {
3415 #if DEBUG_NO_CONFIG_FILE
3416     Debug("setup", "ignoring level directory '%s'", directory_path);
3417 #endif
3418
3419     free(directory_path);
3420     free(filename);
3421
3422     return FALSE;
3423   }
3424
3425   leveldir_new = newTreeInfo();
3426
3427   if (node_parent)
3428     setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3429   else
3430     setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3431
3432   leveldir_new->subdir = getStringCopy(directory_name);
3433
3434   // set all structure fields according to the token/value pairs
3435   ldi = *leveldir_new;
3436   for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3437     setSetupInfo(levelinfo_tokens, i,
3438                  getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3439   *leveldir_new = ldi;
3440
3441   if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3442     setString(&leveldir_new->name, leveldir_new->subdir);
3443
3444   if (leveldir_new->identifier == NULL)
3445     leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3446
3447   if (leveldir_new->name_sorting == NULL)
3448     leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3449
3450   if (node_parent == NULL)              // top level group
3451   {
3452     leveldir_new->basepath = getStringCopy(level_directory);
3453     leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3454   }
3455   else                                  // sub level group
3456   {
3457     leveldir_new->basepath = getStringCopy(node_parent->basepath);
3458     leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3459   }
3460
3461   leveldir_new->last_level =
3462     leveldir_new->first_level + leveldir_new->levels - 1;
3463
3464   leveldir_new->in_user_dir =
3465     (!strEqual(leveldir_new->basepath, options.level_directory));
3466
3467   // adjust some settings if user's private level directory was detected
3468   if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3469       leveldir_new->in_user_dir &&
3470       (strEqual(leveldir_new->subdir, getLoginName()) ||
3471        strEqual(leveldir_new->name,   getLoginName()) ||
3472        strEqual(leveldir_new->author, getRealName())))
3473   {
3474     leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3475     leveldir_new->readonly = FALSE;
3476   }
3477
3478   leveldir_new->user_defined =
3479     (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3480
3481   setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3482
3483   leveldir_new->handicap_level =        // set handicap to default value
3484     (leveldir_new->user_defined || !leveldir_new->handicap ?
3485      leveldir_new->last_level : leveldir_new->first_level);
3486
3487   DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3488
3489   pushTreeInfo(node_first, leveldir_new);
3490
3491   freeSetupFileHash(setup_file_hash);
3492
3493   if (leveldir_new->level_group)
3494   {
3495     // create node to link back to current level directory
3496     createParentTreeInfoNode(leveldir_new);
3497
3498     // recursively step into sub-directory and look for more level series
3499     LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3500                               leveldir_new, directory_path);
3501   }
3502
3503   free(directory_path);
3504   free(filename);
3505
3506   return TRUE;
3507 }
3508
3509 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3510                                       TreeInfo *node_parent,
3511                                       char *level_directory)
3512 {
3513   // ---------- 1st stage: process any level set zip files ----------
3514
3515   ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3516
3517   // ---------- 2nd stage: check for level set directories ----------
3518
3519   Directory *dir;
3520   DirectoryEntry *dir_entry;
3521   boolean valid_entry_found = FALSE;
3522
3523   if ((dir = openDirectory(level_directory)) == NULL)
3524   {
3525     Warn("cannot read level directory '%s'", level_directory);
3526
3527     return;
3528   }
3529
3530   while ((dir_entry = readDirectory(dir)) != NULL)      // loop all entries
3531   {
3532     char *directory_name = dir_entry->basename;
3533     char *directory_path = getPath2(level_directory, directory_name);
3534
3535     // skip entries for current and parent directory
3536     if (strEqual(directory_name, ".") ||
3537         strEqual(directory_name, ".."))
3538     {
3539       free(directory_path);
3540
3541       continue;
3542     }
3543
3544     // find out if directory entry is itself a directory
3545     if (!dir_entry->is_directory)                       // not a directory
3546     {
3547       free(directory_path);
3548
3549       continue;
3550     }
3551
3552     free(directory_path);
3553
3554     if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3555         strEqual(directory_name, SOUNDS_DIRECTORY) ||
3556         strEqual(directory_name, MUSIC_DIRECTORY))
3557       continue;
3558
3559     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3560                                                     level_directory,
3561                                                     directory_name);
3562   }
3563
3564   closeDirectory(dir);
3565
3566   // special case: top level directory may directly contain "levelinfo.conf"
3567   if (node_parent == NULL && !valid_entry_found)
3568   {
3569     // check if this directory directly contains a file "levelinfo.conf"
3570     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3571                                                     level_directory, ".");
3572   }
3573
3574   if (!valid_entry_found)
3575     Warn("cannot find any valid level series in directory '%s'",
3576           level_directory);
3577 }
3578
3579 boolean AdjustGraphicsForEMC(void)
3580 {
3581   boolean settings_changed = FALSE;
3582
3583   settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3584   settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3585
3586   return settings_changed;
3587 }
3588
3589 boolean AdjustSoundsForEMC(void)
3590 {
3591   boolean settings_changed = FALSE;
3592
3593   settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
3594   settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
3595
3596   return settings_changed;
3597 }
3598
3599 void LoadLevelInfo(void)
3600 {
3601   InitUserLevelDirectory(getLoginName());
3602
3603   DrawInitText("Loading level series", 120, FC_GREEN);
3604
3605   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3606   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3607
3608   leveldir_first = createTopTreeInfoNode(leveldir_first);
3609
3610   /* after loading all level set information, clone the level directory tree
3611      and remove all level sets without levels (these may still contain artwork
3612      to be offered in the setup menu as "custom artwork", and are therefore
3613      checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3614   leveldir_first_all = leveldir_first;
3615   cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3616
3617   AdjustGraphicsForEMC();
3618   AdjustSoundsForEMC();
3619
3620   // before sorting, the first entries will be from the user directory
3621   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3622
3623   if (leveldir_first == NULL)
3624     Fail("cannot find any valid level series in any directory");
3625
3626   sortTreeInfo(&leveldir_first);
3627
3628 #if ENABLE_UNUSED_CODE
3629   dumpTreeInfo(leveldir_first, 0);
3630 #endif
3631 }
3632
3633 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3634                                               TreeInfo *node_parent,
3635                                               char *base_directory,
3636                                               char *directory_name, int type)
3637 {
3638   char *directory_path = getPath2(base_directory, directory_name);
3639   char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3640   SetupFileHash *setup_file_hash = NULL;
3641   TreeInfo *artwork_new = NULL;
3642   int i;
3643
3644   if (fileExists(filename))
3645     setup_file_hash = loadSetupFileHash(filename);
3646
3647   if (setup_file_hash == NULL)  // no config file -- look for artwork files
3648   {
3649     Directory *dir;
3650     DirectoryEntry *dir_entry;
3651     boolean valid_file_found = FALSE;
3652
3653     if ((dir = openDirectory(directory_path)) != NULL)
3654     {
3655       while ((dir_entry = readDirectory(dir)) != NULL)
3656       {
3657         if (FileIsArtworkType(dir_entry->filename, type))
3658         {
3659           valid_file_found = TRUE;
3660
3661           break;
3662         }
3663       }
3664
3665       closeDirectory(dir);
3666     }
3667
3668     if (!valid_file_found)
3669     {
3670 #if DEBUG_NO_CONFIG_FILE
3671       if (!strEqual(directory_name, "."))
3672         Debug("setup", "ignoring artwork directory '%s'", directory_path);
3673 #endif
3674
3675       free(directory_path);
3676       free(filename);
3677
3678       return FALSE;
3679     }
3680   }
3681
3682   artwork_new = newTreeInfo();
3683
3684   if (node_parent)
3685     setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3686   else
3687     setTreeInfoToDefaults(artwork_new, type);
3688
3689   artwork_new->subdir = getStringCopy(directory_name);
3690
3691   if (setup_file_hash)  // (before defining ".color" and ".class_desc")
3692   {
3693     // set all structure fields according to the token/value pairs
3694     ldi = *artwork_new;
3695     for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3696       setSetupInfo(levelinfo_tokens, i,
3697                    getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3698     *artwork_new = ldi;
3699
3700     if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3701       setString(&artwork_new->name, artwork_new->subdir);
3702
3703     if (artwork_new->identifier == NULL)
3704       artwork_new->identifier = getStringCopy(artwork_new->subdir);
3705
3706     if (artwork_new->name_sorting == NULL)
3707       artwork_new->name_sorting = getStringCopy(artwork_new->name);
3708   }
3709
3710   if (node_parent == NULL)              // top level group
3711   {
3712     artwork_new->basepath = getStringCopy(base_directory);
3713     artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3714   }
3715   else                                  // sub level group
3716   {
3717     artwork_new->basepath = getStringCopy(node_parent->basepath);
3718     artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3719   }
3720
3721   artwork_new->in_user_dir =
3722     (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3723
3724   setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3725
3726   if (setup_file_hash == NULL)  // (after determining ".user_defined")
3727   {
3728     if (strEqual(artwork_new->subdir, "."))
3729     {
3730       if (artwork_new->user_defined)
3731       {
3732         setString(&artwork_new->identifier, "private");
3733         artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3734       }
3735       else
3736       {
3737         setString(&artwork_new->identifier, "classic");
3738         artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3739       }
3740
3741       setString(&artwork_new->class_desc,
3742                 getLevelClassDescription(artwork_new));
3743     }
3744     else
3745     {
3746       setString(&artwork_new->identifier, artwork_new->subdir);
3747     }
3748
3749     setString(&artwork_new->name, artwork_new->identifier);
3750     setString(&artwork_new->name_sorting, artwork_new->name);
3751   }
3752
3753   pushTreeInfo(node_first, artwork_new);
3754
3755   freeSetupFileHash(setup_file_hash);
3756
3757   free(directory_path);
3758   free(filename);
3759
3760   return TRUE;
3761 }
3762
3763 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3764                                           TreeInfo *node_parent,
3765                                           char *base_directory, int type)
3766 {
3767   // ---------- 1st stage: process any artwork set zip files ----------
3768
3769   ProcessZipFilesInDirectory(base_directory, type);
3770
3771   // ---------- 2nd stage: check for artwork set directories ----------
3772
3773   Directory *dir;
3774   DirectoryEntry *dir_entry;
3775   boolean valid_entry_found = FALSE;
3776
3777   if ((dir = openDirectory(base_directory)) == NULL)
3778   {
3779     // display error if directory is main "options.graphics_directory" etc.
3780     if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3781       Warn("cannot read directory '%s'", base_directory);
3782
3783     return;
3784   }
3785
3786   while ((dir_entry = readDirectory(dir)) != NULL)      // loop all entries
3787   {
3788     char *directory_name = dir_entry->basename;
3789     char *directory_path = getPath2(base_directory, directory_name);
3790
3791     // skip directory entries for current and parent directory
3792     if (strEqual(directory_name, ".") ||
3793         strEqual(directory_name, ".."))
3794     {
3795       free(directory_path);
3796
3797       continue;
3798     }
3799
3800     // skip directory entries which are not a directory
3801     if (!dir_entry->is_directory)                       // not a directory
3802     {
3803       free(directory_path);
3804
3805       continue;
3806     }
3807
3808     free(directory_path);
3809
3810     // check if this directory contains artwork with or without config file
3811     valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3812                                                         base_directory,
3813                                                         directory_name, type);
3814   }
3815
3816   closeDirectory(dir);
3817
3818   // check if this directory directly contains artwork itself
3819   valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3820                                                       base_directory, ".",
3821                                                       type);
3822   if (!valid_entry_found)
3823     Warn("cannot find any valid artwork in directory '%s'", base_directory);
3824 }
3825
3826 static TreeInfo *getDummyArtworkInfo(int type)
3827 {
3828   // this is only needed when there is completely no artwork available
3829   TreeInfo *artwork_new = newTreeInfo();
3830
3831   setTreeInfoToDefaults(artwork_new, type);
3832
3833   setString(&artwork_new->subdir,   UNDEFINED_FILENAME);
3834   setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3835   setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3836
3837   setString(&artwork_new->identifier,   UNDEFINED_FILENAME);
3838   setString(&artwork_new->name,         UNDEFINED_FILENAME);
3839   setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3840
3841   return artwork_new;
3842 }
3843
3844 void SetCurrentArtwork(int type)
3845 {
3846   ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
3847   ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
3848   char *setup_set = SETUP_ARTWORK_SET(setup, type);
3849   char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
3850
3851   // set current artwork to artwork configured in setup menu
3852   *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
3853
3854   // if not found, set current artwork to default artwork
3855   if (*current_ptr == NULL)
3856     *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
3857
3858   // if not found, set current artwork to first artwork in tree
3859   if (*current_ptr == NULL)
3860     *current_ptr = getFirstValidTreeInfoEntry(first_node);
3861 }
3862
3863 void ChangeCurrentArtworkIfNeeded(int type)
3864 {
3865   char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
3866   char *setup_set = SETUP_ARTWORK_SET(setup, type);
3867
3868   if (!strEqual(current_identifier, setup_set))
3869     SetCurrentArtwork(type);
3870 }
3871
3872 void LoadArtworkInfo(void)
3873 {
3874   LoadArtworkInfoCache();
3875
3876   DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3877
3878   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3879                                 options.graphics_directory,
3880                                 TREE_TYPE_GRAPHICS_DIR);
3881   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3882                                 getUserGraphicsDir(),
3883                                 TREE_TYPE_GRAPHICS_DIR);
3884
3885   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3886                                 options.sounds_directory,
3887                                 TREE_TYPE_SOUNDS_DIR);
3888   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3889                                 getUserSoundsDir(),
3890                                 TREE_TYPE_SOUNDS_DIR);
3891
3892   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3893                                 options.music_directory,
3894                                 TREE_TYPE_MUSIC_DIR);
3895   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3896                                 getUserMusicDir(),
3897                                 TREE_TYPE_MUSIC_DIR);
3898
3899   if (artwork.gfx_first == NULL)
3900     artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3901   if (artwork.snd_first == NULL)
3902     artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3903   if (artwork.mus_first == NULL)
3904     artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3905
3906   // before sorting, the first entries will be from the user directory
3907   SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
3908   SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
3909   SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
3910
3911   artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3912   artwork.snd_current_identifier = artwork.snd_current->identifier;
3913   artwork.mus_current_identifier = artwork.mus_current->identifier;
3914
3915 #if ENABLE_UNUSED_CODE
3916   Debug("setup:LoadArtworkInfo", "graphics set == %s",
3917         artwork.gfx_current_identifier);
3918   Debug("setup:LoadArtworkInfo", "sounds set == %s",
3919         artwork.snd_current_identifier);
3920   Debug("setup:LoadArtworkInfo", "music set == %s",
3921         artwork.mus_current_identifier);
3922 #endif
3923
3924   sortTreeInfo(&artwork.gfx_first);
3925   sortTreeInfo(&artwork.snd_first);
3926   sortTreeInfo(&artwork.mus_first);
3927
3928 #if ENABLE_UNUSED_CODE
3929   dumpTreeInfo(artwork.gfx_first, 0);
3930   dumpTreeInfo(artwork.snd_first, 0);
3931   dumpTreeInfo(artwork.mus_first, 0);
3932 #endif
3933 }
3934
3935 static void MoveArtworkInfoIntoSubTree(ArtworkDirTree **artwork_node)
3936 {
3937   ArtworkDirTree *artwork_new = newTreeInfo();
3938   char *top_node_name = "standalone artwork";
3939
3940   setTreeInfoToDefaults(artwork_new, (*artwork_node)->type);
3941
3942   artwork_new->level_group = TRUE;
3943
3944   setString(&artwork_new->identifier,   top_node_name);
3945   setString(&artwork_new->name,         top_node_name);
3946   setString(&artwork_new->name_sorting, top_node_name);
3947
3948   // create node to link back to current custom artwork directory
3949   createParentTreeInfoNode(artwork_new);
3950
3951   // move existing custom artwork tree into newly created sub-tree
3952   artwork_new->node_group->next = *artwork_node;
3953
3954   // change custom artwork tree to contain only newly created node
3955   *artwork_node = artwork_new;
3956 }
3957
3958 static void LoadArtworkInfoFromLevelInfoExt(ArtworkDirTree **artwork_node,
3959                                             ArtworkDirTree *node_parent,
3960                                             LevelDirTree *level_node,
3961                                             boolean empty_level_set_mode)
3962 {
3963   int type = (*artwork_node)->type;
3964
3965   // recursively check all level directories for artwork sub-directories
3966
3967   while (level_node)
3968   {
3969     boolean empty_level_set = (level_node->levels == 0);
3970
3971     // check all tree entries for artwork, but skip parent link entries
3972     if (!level_node->parent_link && empty_level_set == empty_level_set_mode)
3973     {
3974       TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3975       boolean cached = (artwork_new != NULL);
3976
3977       if (cached)
3978       {
3979         pushTreeInfo(artwork_node, artwork_new);
3980       }
3981       else
3982       {
3983         TreeInfo *topnode_last = *artwork_node;
3984         char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3985                               ARTWORK_DIRECTORY(type));
3986
3987         LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3988
3989         if (topnode_last != *artwork_node)      // check for newly added node
3990         {
3991           artwork_new = *artwork_node;
3992
3993           setString(&artwork_new->identifier,   level_node->subdir);
3994           setString(&artwork_new->name,         level_node->name);
3995           setString(&artwork_new->name_sorting, level_node->name_sorting);
3996
3997           artwork_new->sort_priority = level_node->sort_priority;
3998           artwork_new->in_user_dir = level_node->in_user_dir;
3999
4000           update_artworkinfo_cache = TRUE;
4001         }
4002
4003         free(path);
4004       }
4005
4006       // insert artwork info (from old cache or filesystem) into new cache
4007       if (artwork_new != NULL)
4008         setArtworkInfoCacheEntry(artwork_new, level_node, type);
4009     }
4010
4011     DrawInitText(level_node->name, 150, FC_YELLOW);
4012
4013     if (level_node->node_group != NULL)
4014     {
4015       TreeInfo *artwork_new = newTreeInfo();
4016
4017       if (node_parent)
4018         setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4019       else
4020         setTreeInfoToDefaults(artwork_new, type);
4021
4022       artwork_new->level_group = TRUE;
4023
4024       setString(&artwork_new->identifier,   level_node->subdir);
4025
4026       if (node_parent == NULL)          // check for top tree node
4027       {
4028         char *top_node_name = (empty_level_set_mode ?
4029                                "artwork for certain level sets" :
4030                                "artwork included in level sets");
4031
4032         setString(&artwork_new->name,         top_node_name);
4033         setString(&artwork_new->name_sorting, top_node_name);
4034       }
4035       else
4036       {
4037         setString(&artwork_new->name,         level_node->name);
4038         setString(&artwork_new->name_sorting, level_node->name_sorting);
4039       }
4040
4041       pushTreeInfo(artwork_node, artwork_new);
4042
4043       // create node to link back to current custom artwork directory
4044       createParentTreeInfoNode(artwork_new);
4045
4046       // recursively step into sub-directory and look for more custom artwork
4047       LoadArtworkInfoFromLevelInfoExt(&artwork_new->node_group, artwork_new,
4048                                       level_node->node_group,
4049                                       empty_level_set_mode);
4050
4051       // if sub-tree has no custom artwork at all, remove it
4052       if (artwork_new->node_group->next == NULL)
4053         removeTreeInfo(artwork_node);
4054     }
4055
4056     level_node = level_node->next;
4057   }
4058 }
4059
4060 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node)
4061 {
4062   // move peviously loaded artwork tree into separate sub-tree
4063   MoveArtworkInfoIntoSubTree(artwork_node);
4064
4065   // load artwork from level sets into separate sub-trees
4066   LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, TRUE);
4067   LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, FALSE);
4068
4069   // add top tree node over all three separate sub-trees
4070   *artwork_node = createTopTreeInfoNode(*artwork_node);
4071
4072   // set all parent links (back links) in complete artwork tree
4073   setTreeInfoParentNodes(*artwork_node, NULL);
4074 }
4075
4076 void LoadLevelArtworkInfo(void)
4077 {
4078   print_timestamp_init("LoadLevelArtworkInfo");
4079
4080   DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
4081
4082   print_timestamp_time("DrawTimeText");
4083
4084   LoadArtworkInfoFromLevelInfo(&artwork.gfx_first);
4085   print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4086   LoadArtworkInfoFromLevelInfo(&artwork.snd_first);
4087   print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4088   LoadArtworkInfoFromLevelInfo(&artwork.mus_first);
4089   print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4090
4091   SaveArtworkInfoCache();
4092
4093   print_timestamp_time("SaveArtworkInfoCache");
4094
4095   // needed for reloading level artwork not known at ealier stage
4096   ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
4097   ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
4098   ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
4099
4100   print_timestamp_time("getTreeInfoFromIdentifier");
4101
4102   sortTreeInfo(&artwork.gfx_first);
4103   sortTreeInfo(&artwork.snd_first);
4104   sortTreeInfo(&artwork.mus_first);
4105
4106   print_timestamp_time("sortTreeInfo");
4107
4108 #if ENABLE_UNUSED_CODE
4109   dumpTreeInfo(artwork.gfx_first, 0);
4110   dumpTreeInfo(artwork.snd_first, 0);
4111   dumpTreeInfo(artwork.mus_first, 0);
4112 #endif
4113
4114   print_timestamp_done("LoadLevelArtworkInfo");
4115 }
4116
4117 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
4118                                        char *tree_subdir_new, int type)
4119 {
4120   if (tree_node_old == NULL)
4121   {
4122     if (type == TREE_TYPE_LEVEL_DIR)
4123     {
4124       // get level info tree node of personal user level set
4125       tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
4126
4127       // this may happen if "setup.internal.create_user_levelset" is FALSE
4128       // or if file "levelinfo.conf" is missing in personal user level set
4129       if (tree_node_old == NULL)
4130         tree_node_old = leveldir_first->node_group;
4131     }
4132     else
4133     {
4134       // get artwork info tree node of first artwork set
4135       tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4136     }
4137   }
4138
4139   if (tree_dir == NULL)
4140     tree_dir = TREE_USERDIR(type);
4141
4142   if (tree_node_old   == NULL ||
4143       tree_dir        == NULL ||
4144       tree_subdir_new == NULL)          // should not happen
4145     return FALSE;
4146
4147   int draw_deactivation_mask = GetDrawDeactivationMask();
4148
4149   // override draw deactivation mask (temporarily disable drawing)
4150   SetDrawDeactivationMask(REDRAW_ALL);
4151
4152   if (type == TREE_TYPE_LEVEL_DIR)
4153   {
4154     // load new level set config and add it next to first user level set
4155     LoadLevelInfoFromLevelConf(&tree_node_old->next,
4156                                tree_node_old->node_parent,
4157                                tree_dir, tree_subdir_new);
4158   }
4159   else
4160   {
4161     // load new artwork set config and add it next to first artwork set
4162     LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4163                                    tree_node_old->node_parent,
4164                                    tree_dir, tree_subdir_new, type);
4165   }
4166
4167   // set draw deactivation mask to previous value
4168   SetDrawDeactivationMask(draw_deactivation_mask);
4169
4170   // get first node of level or artwork info tree
4171   TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4172
4173   // get tree info node of newly added level or artwork set
4174   TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4175                                                       tree_subdir_new);
4176
4177   if (tree_node_new == NULL)            // should not happen
4178     return FALSE;
4179
4180   // correct top link and parent node link of newly created tree node
4181   tree_node_new->node_top    = tree_node_old->node_top;
4182   tree_node_new->node_parent = tree_node_old->node_parent;
4183
4184   // sort tree info to adjust position of newly added tree set
4185   sortTreeInfo(tree_node_first);
4186
4187   return TRUE;
4188 }
4189
4190 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4191                           char *tree_subdir_new, int type)
4192 {
4193   if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4194     Fail("internal tree info structure corrupted -- aborting");
4195 }
4196
4197 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4198 {
4199   AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4200 }
4201
4202 char *getArtworkIdentifierForUserLevelSet(int type)
4203 {
4204   char *classic_artwork_set = getClassicArtworkSet(type);
4205
4206   // check for custom artwork configured in "levelinfo.conf"
4207   char *leveldir_artwork_set =
4208     *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4209   boolean has_leveldir_artwork_set =
4210     (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4211                                                classic_artwork_set));
4212
4213   // check for custom artwork in sub-directory "graphics" etc.
4214   TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4215   char *leveldir_identifier = leveldir_current->identifier;
4216   boolean has_artwork_subdir =
4217     (getTreeInfoFromIdentifier(artwork_first_node,
4218                                leveldir_identifier) != NULL);
4219
4220   return (has_leveldir_artwork_set ? leveldir_artwork_set :
4221           has_artwork_subdir       ? leveldir_identifier :
4222           classic_artwork_set);
4223 }
4224
4225 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4226 {
4227   char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4228   TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4229   TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4230
4231   if (ti == NULL)
4232   {
4233     ti = getTreeInfoFromIdentifier(artwork_first_node,
4234                                    ARTWORK_DEFAULT_SUBDIR(type));
4235     if (ti == NULL)
4236       Fail("cannot find default graphics -- should not happen");
4237   }
4238
4239   return ti;
4240 }
4241
4242 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4243 {
4244   char *graphics_set =
4245     getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4246   char *sounds_set =
4247     getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4248   char *music_set =
4249     getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4250
4251   return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4252           !strEqual(sounds_set,   SND_CLASSIC_SUBDIR) ||
4253           !strEqual(music_set,    MUS_CLASSIC_SUBDIR));
4254 }
4255
4256 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4257                            char *level_author, int num_levels)
4258 {
4259   char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4260   char *filename_tmp = getStringCat2(filename, ".tmp");
4261   FILE *file = NULL;
4262   FILE *file_tmp = NULL;
4263   char line[MAX_LINE_LEN];
4264   boolean success = FALSE;
4265   LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4266                                                      level_subdir);
4267   // update values in level directory tree
4268
4269   if (level_name != NULL)
4270     setString(&leveldir->name, level_name);
4271
4272   if (level_author != NULL)
4273     setString(&leveldir->author, level_author);
4274
4275   if (num_levels != -1)
4276     leveldir->levels = num_levels;
4277
4278   // update values that depend on other values
4279
4280   setString(&leveldir->name_sorting, leveldir->name);
4281
4282   leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4283
4284   // sort order of level sets may have changed
4285   sortTreeInfo(&leveldir_first);
4286
4287   if ((file     = fopen(filename,     MODE_READ)) &&
4288       (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4289   {
4290     while (fgets(line, MAX_LINE_LEN, file))
4291     {
4292       if (strPrefix(line, "name:") && level_name != NULL)
4293         fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4294       else if (strPrefix(line, "author:") && level_author != NULL)
4295         fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4296       else if (strPrefix(line, "levels:") && num_levels != -1)
4297         fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4298       else
4299         fputs(line, file_tmp);
4300     }
4301
4302     success = TRUE;
4303   }
4304
4305   if (file)
4306     fclose(file);
4307
4308   if (file_tmp)
4309     fclose(file_tmp);
4310
4311   if (success)
4312     success = (rename(filename_tmp, filename) == 0);
4313
4314   free(filename);
4315   free(filename_tmp);
4316
4317   return success;
4318 }
4319
4320 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4321                            char *level_author, int num_levels,
4322                            boolean use_artwork_set)
4323 {
4324   LevelDirTree *level_info;
4325   char *filename;
4326   FILE *file;
4327   int i;
4328
4329   // create user level sub-directory, if needed
4330   createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
4331
4332   filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4333
4334   if (!(file = fopen(filename, MODE_WRITE)))
4335   {
4336     Warn("cannot write level info file '%s'", filename);
4337
4338     free(filename);
4339
4340     return FALSE;
4341   }
4342
4343   level_info = newTreeInfo();
4344
4345   // always start with reliable default values
4346   setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4347
4348   setString(&level_info->name, level_name);
4349   setString(&level_info->author, level_author);
4350   level_info->levels = num_levels;
4351   level_info->first_level = 1;
4352   level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4353   level_info->readonly = FALSE;
4354
4355   if (use_artwork_set)
4356   {
4357     level_info->graphics_set =
4358       getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4359     level_info->sounds_set =
4360       getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4361     level_info->music_set =
4362       getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4363   }
4364
4365   token_value_position = TOKEN_VALUE_POSITION_SHORT;
4366
4367   fprintFileHeader(file, LEVELINFO_FILENAME);
4368
4369   ldi = *level_info;
4370   for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4371   {
4372     if (i == LEVELINFO_TOKEN_NAME ||
4373         i == LEVELINFO_TOKEN_AUTHOR ||
4374         i == LEVELINFO_TOKEN_LEVELS ||
4375         i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4376         i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4377         i == LEVELINFO_TOKEN_READONLY ||
4378         (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4379                              i == LEVELINFO_TOKEN_SOUNDS_SET ||
4380                              i == LEVELINFO_TOKEN_MUSIC_SET)))
4381       fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4382
4383     // just to make things nicer :)
4384     if (i == LEVELINFO_TOKEN_AUTHOR ||
4385         i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4386         (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4387       fprintf(file, "\n");      
4388   }
4389
4390   token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4391
4392   fclose(file);
4393
4394   SetFilePermissions(filename, PERMS_PRIVATE);
4395
4396   freeTreeInfo(level_info);
4397   free(filename);
4398
4399   return TRUE;
4400 }
4401
4402 static void SaveUserLevelInfo(void)
4403 {
4404   CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4405 }
4406
4407 char *getSetupValue(int type, void *value)
4408 {
4409   static char value_string[MAX_LINE_LEN];
4410
4411   if (value == NULL)
4412     return NULL;
4413
4414   switch (type)
4415   {
4416     case TYPE_BOOLEAN:
4417       strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4418       break;
4419
4420     case TYPE_SWITCH:
4421       strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4422       break;
4423
4424     case TYPE_SWITCH3:
4425       strcpy(value_string, (*(int *)value == AUTO  ? "auto" :
4426                             *(int *)value == FALSE ? "off" : "on"));
4427       break;
4428
4429     case TYPE_YES_NO:
4430       strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4431       break;
4432
4433     case TYPE_YES_NO_AUTO:
4434       strcpy(value_string, (*(int *)value == AUTO  ? "auto" :
4435                             *(int *)value == FALSE ? "no" : "yes"));
4436       break;
4437
4438     case TYPE_ECS_AGA:
4439       strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4440       break;
4441
4442     case TYPE_KEY:
4443       strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4444       break;
4445
4446     case TYPE_KEY_X11:
4447       strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4448       break;
4449
4450     case TYPE_INTEGER:
4451       sprintf(value_string, "%d", *(int *)value);
4452       break;
4453
4454     case TYPE_STRING:
4455       if (*(char **)value == NULL)
4456         return NULL;
4457
4458       strcpy(value_string, *(char **)value);
4459       break;
4460
4461     case TYPE_PLAYER:
4462       sprintf(value_string, "player_%d", *(int *)value + 1);
4463       break;
4464
4465     default:
4466       value_string[0] = '\0';
4467       break;
4468   }
4469
4470   if (type & TYPE_GHOSTED)
4471     strcpy(value_string, "n/a");
4472
4473   return value_string;
4474 }
4475
4476 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4477 {
4478   int i;
4479   char *line;
4480   static char token_string[MAX_LINE_LEN];
4481   int token_type = token_info[token_nr].type;
4482   void *setup_value = token_info[token_nr].value;
4483   char *token_text = token_info[token_nr].text;
4484   char *value_string = getSetupValue(token_type, setup_value);
4485
4486   // build complete token string
4487   sprintf(token_string, "%s%s", prefix, token_text);
4488
4489   // build setup entry line
4490   line = getFormattedSetupEntry(token_string, value_string);
4491
4492   if (token_type == TYPE_KEY_X11)
4493   {
4494     Key key = *(Key *)setup_value;
4495     char *keyname = getKeyNameFromKey(key);
4496
4497     // add comment, if useful
4498     if (!strEqual(keyname, "(undefined)") &&
4499         !strEqual(keyname, "(unknown)"))
4500     {
4501       // add at least one whitespace
4502       strcat(line, " ");
4503       for (i = strlen(line); i < token_comment_position; i++)
4504         strcat(line, " ");
4505
4506       strcat(line, "# ");
4507       strcat(line, keyname);
4508     }
4509   }
4510
4511   return line;
4512 }
4513
4514 static void InitLastPlayedLevels_ParentNode(void)
4515 {
4516   LevelDirTree **leveldir_top = &leveldir_first->node_group->next;
4517   LevelDirTree *leveldir_new = NULL;
4518
4519   // check if parent node for last played levels already exists
4520   if (strEqual((*leveldir_top)->identifier, TOKEN_STR_LAST_LEVEL_SERIES))
4521     return;
4522
4523   leveldir_new = newTreeInfo();
4524
4525   setTreeInfoToDefaultsFromParent(leveldir_new, leveldir_first);
4526
4527   leveldir_new->level_group = TRUE;
4528   leveldir_new->sort_priority = LEVELCLASS_LAST_PLAYED_LEVEL;
4529
4530   setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES);
4531   setString(&leveldir_new->name, "<< (last played level sets)");
4532   setString(&leveldir_new->name_sorting, leveldir_new->name);
4533
4534   pushTreeInfo(leveldir_top, leveldir_new);
4535
4536   // create node to link back to current level directory
4537   createParentTreeInfoNode(leveldir_new);
4538 }
4539
4540 void UpdateLastPlayedLevels_TreeInfo(void)
4541 {
4542   char **last_level_series = setup.level_setup.last_level_series;
4543   boolean reset_leveldir_current = FALSE;
4544   LevelDirTree *leveldir_last;
4545   TreeInfo **node_new = NULL;
4546   int i;
4547
4548   if (last_level_series[0] == NULL)
4549     return;
4550
4551   InitLastPlayedLevels_ParentNode();
4552
4553   // check if current level set is from "last played" sub-tree to be rebuilt
4554   reset_leveldir_current = strEqual(leveldir_current->node_parent->identifier,
4555                                     TOKEN_STR_LAST_LEVEL_SERIES);
4556
4557   leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first,
4558                                                TOKEN_STR_LAST_LEVEL_SERIES,
4559                                                TRUE);
4560   if (leveldir_last == NULL)
4561     return;
4562
4563   node_new = &leveldir_last->node_group->next;
4564
4565   freeTreeInfo(*node_new);
4566
4567   *node_new = NULL;
4568
4569   for (i = 0; last_level_series[i] != NULL; i++)
4570   {
4571     LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first,
4572                                                         last_level_series[i]);
4573     if (node_last == NULL)
4574       continue;
4575
4576     *node_new = getTreeInfoCopy(node_last);     // copy complete node
4577
4578     (*node_new)->node_top = &leveldir_first;    // correct top node link
4579     (*node_new)->node_parent = leveldir_last;   // correct parent node link
4580
4581     (*node_new)->is_copy = TRUE;                // mark entry as node copy
4582
4583     (*node_new)->node_group = NULL;
4584     (*node_new)->next = NULL;
4585
4586     (*node_new)->cl_first = -1;                 // force setting tree cursor
4587
4588     node_new = &((*node_new)->next);
4589   }
4590
4591   if (reset_leveldir_current)
4592     leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4593                                                  last_level_series[0]);
4594 }
4595
4596 static void UpdateLastPlayedLevels_List(void)
4597 {
4598   char **last_level_series = setup.level_setup.last_level_series;
4599   int pos = MAX_LEVELDIR_HISTORY - 1;
4600   int i;
4601
4602   // search for potentially already existing entry in list of level sets
4603   for (i = 0; last_level_series[i] != NULL; i++)
4604     if (strEqual(last_level_series[i], leveldir_current->identifier))
4605       pos = i;
4606
4607   // move list of level sets one entry down (using potentially free entry)
4608   for (i = pos; i > 0; i--)
4609     setString(&last_level_series[i], last_level_series[i - 1]);
4610
4611   // put last played level set at top position
4612   setString(&last_level_series[0], leveldir_current->identifier);
4613 }
4614
4615 void LoadLevelSetup_LastSeries(void)
4616 {
4617   // --------------------------------------------------------------------------
4618   // ~/.<program>/levelsetup.conf
4619   // --------------------------------------------------------------------------
4620
4621   char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4622   SetupFileHash *level_setup_hash = NULL;
4623   int pos = 0;
4624   int i;
4625
4626   // always start with reliable default values
4627   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4628
4629   // start with empty history of last played level sets
4630   setString(&setup.level_setup.last_level_series[0], NULL);
4631
4632   if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4633   {
4634     leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4635                                                  DEFAULT_LEVELSET);
4636     if (leveldir_current == NULL)
4637       leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4638   }
4639
4640   if ((level_setup_hash = loadSetupFileHash(filename)))
4641   {
4642     char *last_level_series =
4643       getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4644
4645     leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4646                                                  last_level_series);
4647     if (leveldir_current == NULL)
4648       leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4649
4650     for (i = 0; i < MAX_LEVELDIR_HISTORY; i++)
4651     {
4652       char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4653       LevelDirTree *leveldir_last;
4654
4655       sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4656
4657       last_level_series = getHashEntry(level_setup_hash, token);
4658
4659       leveldir_last = getTreeInfoFromIdentifier(leveldir_first,
4660                                                 last_level_series);
4661       if (leveldir_last != NULL)
4662         setString(&setup.level_setup.last_level_series[pos++],
4663                   last_level_series);
4664     }
4665
4666     setString(&setup.level_setup.last_level_series[pos], NULL);
4667
4668     freeSetupFileHash(level_setup_hash);
4669   }
4670   else
4671   {
4672     Debug("setup", "using default setup values");
4673   }
4674
4675   free(filename);
4676 }
4677
4678 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4679 {
4680   // --------------------------------------------------------------------------
4681   // ~/.<program>/levelsetup.conf
4682   // --------------------------------------------------------------------------
4683
4684   // check if the current level directory structure is available at this point
4685   if (leveldir_current == NULL)
4686     return;
4687
4688   char **last_level_series = setup.level_setup.last_level_series;
4689   char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4690   FILE *file;
4691   int i;
4692
4693   InitUserDataDirectory();
4694
4695   UpdateLastPlayedLevels_List();
4696
4697   if (!(file = fopen(filename, MODE_WRITE)))
4698   {
4699     Warn("cannot write setup file '%s'", filename);
4700
4701     free(filename);
4702
4703     return;
4704   }
4705
4706   fprintFileHeader(file, LEVELSETUP_FILENAME);
4707
4708   if (deactivate_last_level_series)
4709     fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4710
4711   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4712                                                leveldir_current->identifier));
4713
4714   for (i = 0; last_level_series[i] != NULL; i++)
4715   {
4716     char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4717
4718     sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4719
4720     fprintf(file, "%s\n", getFormattedSetupEntry(token, last_level_series[i]));
4721   }
4722
4723   fclose(file);
4724
4725   SetFilePermissions(filename, PERMS_PRIVATE);
4726
4727   free(filename);
4728 }
4729
4730 void SaveLevelSetup_LastSeries(void)
4731 {
4732   SaveLevelSetup_LastSeries_Ext(FALSE);
4733 }
4734
4735 void SaveLevelSetup_LastSeries_Deactivate(void)
4736 {
4737   SaveLevelSetup_LastSeries_Ext(TRUE);
4738 }
4739
4740 static void checkSeriesInfo(void)
4741 {
4742   static char *level_directory = NULL;
4743   Directory *dir;
4744 #if 0
4745   DirectoryEntry *dir_entry;
4746 #endif
4747
4748   checked_free(level_directory);
4749
4750   // check for more levels besides the 'levels' field of 'levelinfo.conf'
4751
4752   level_directory = getPath2((leveldir_current->in_user_dir ?
4753                               getUserLevelDir(NULL) :
4754                               options.level_directory),
4755                              leveldir_current->fullpath);
4756
4757   if ((dir = openDirectory(level_directory)) == NULL)
4758   {
4759     Warn("cannot read level directory '%s'", level_directory);
4760
4761     return;
4762   }
4763
4764 #if 0
4765   while ((dir_entry = readDirectory(dir)) != NULL)      // loop all entries
4766   {
4767     if (strlen(dir_entry->basename) > 4 &&
4768         dir_entry->basename[3] == '.' &&
4769         strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
4770     {
4771       char levelnum_str[4];
4772       int levelnum_value;
4773
4774       strncpy(levelnum_str, dir_entry->basename, 3);
4775       levelnum_str[3] = '\0';
4776
4777       levelnum_value = atoi(levelnum_str);
4778
4779       if (levelnum_value < leveldir_current->first_level)
4780       {
4781         Warn("additional level %d found", levelnum_value);
4782
4783         leveldir_current->first_level = levelnum_value;
4784       }
4785       else if (levelnum_value > leveldir_current->last_level)
4786       {
4787         Warn("additional level %d found", levelnum_value);
4788
4789         leveldir_current->last_level = levelnum_value;
4790       }
4791     }
4792   }
4793 #endif
4794
4795   closeDirectory(dir);
4796 }
4797
4798 void LoadLevelSetup_SeriesInfo(void)
4799 {
4800   char *filename;
4801   SetupFileHash *level_setup_hash = NULL;
4802   char *level_subdir = leveldir_current->subdir;
4803   int i;
4804
4805   // always start with reliable default values
4806   level_nr = leveldir_current->first_level;
4807
4808   for (i = 0; i < MAX_LEVELS; i++)
4809   {
4810     LevelStats_setPlayed(i, 0);
4811     LevelStats_setSolved(i, 0);
4812   }
4813
4814   checkSeriesInfo();
4815
4816   // --------------------------------------------------------------------------
4817   // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4818   // --------------------------------------------------------------------------
4819
4820   level_subdir = leveldir_current->subdir;
4821
4822   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4823
4824   if ((level_setup_hash = loadSetupFileHash(filename)))
4825   {
4826     char *token_value;
4827
4828     // get last played level in this level set
4829
4830     token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4831
4832     if (token_value)
4833     {
4834       level_nr = atoi(token_value);
4835
4836       if (level_nr < leveldir_current->first_level)
4837         level_nr = leveldir_current->first_level;
4838       if (level_nr > leveldir_current->last_level)
4839         level_nr = leveldir_current->last_level;
4840     }
4841
4842     // get handicap level in this level set
4843
4844     token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4845
4846     if (token_value)
4847     {
4848       int level_nr = atoi(token_value);
4849
4850       if (level_nr < leveldir_current->first_level)
4851         level_nr = leveldir_current->first_level;
4852       if (level_nr > leveldir_current->last_level + 1)
4853         level_nr = leveldir_current->last_level;
4854
4855       if (leveldir_current->user_defined || !leveldir_current->handicap)
4856         level_nr = leveldir_current->last_level;
4857
4858       leveldir_current->handicap_level = level_nr;
4859     }
4860
4861     // get number of played and solved levels in this level set
4862
4863     BEGIN_HASH_ITERATION(level_setup_hash, itr)
4864     {
4865       char *token = HASH_ITERATION_TOKEN(itr);
4866       char *value = HASH_ITERATION_VALUE(itr);
4867
4868       if (strlen(token) == 3 &&
4869           token[0] >= '0' && token[0] <= '9' &&
4870           token[1] >= '0' && token[1] <= '9' &&
4871           token[2] >= '0' && token[2] <= '9')
4872       {
4873         int level_nr = atoi(token);
4874
4875         if (value != NULL)
4876           LevelStats_setPlayed(level_nr, atoi(value));  // read 1st column
4877
4878         value = strchr(value, ' ');
4879
4880         if (value != NULL)
4881           LevelStats_setSolved(level_nr, atoi(value));  // read 2nd column
4882       }
4883     }
4884     END_HASH_ITERATION(hash, itr)
4885
4886     freeSetupFileHash(level_setup_hash);
4887   }
4888   else
4889   {
4890     Debug("setup", "using default setup values");
4891   }
4892
4893   free(filename);
4894 }
4895
4896 void SaveLevelSetup_SeriesInfo(void)
4897 {
4898   char *filename;
4899   char *level_subdir = leveldir_current->subdir;
4900   char *level_nr_str = int2str(level_nr, 0);
4901   char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4902   FILE *file;
4903   int i;
4904
4905   // --------------------------------------------------------------------------
4906   // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4907   // --------------------------------------------------------------------------
4908
4909   InitLevelSetupDirectory(level_subdir);
4910
4911   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4912
4913   if (!(file = fopen(filename, MODE_WRITE)))
4914   {
4915     Warn("cannot write setup file '%s'", filename);
4916
4917     free(filename);
4918
4919     return;
4920   }
4921
4922   fprintFileHeader(file, LEVELSETUP_FILENAME);
4923
4924   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4925                                                level_nr_str));
4926   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4927                                                  handicap_level_str));
4928
4929   for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
4930        i++)
4931   {
4932     if (LevelStats_getPlayed(i) > 0 ||
4933         LevelStats_getSolved(i) > 0)
4934     {
4935       char token[16];
4936       char value[16];
4937
4938       sprintf(token, "%03d", i);
4939       sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
4940
4941       fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
4942     }
4943   }
4944
4945   fclose(file);
4946
4947   SetFilePermissions(filename, PERMS_PRIVATE);
4948
4949   free(filename);
4950 }
4951
4952 int LevelStats_getPlayed(int nr)
4953 {
4954   return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
4955 }
4956
4957 int LevelStats_getSolved(int nr)
4958 {
4959   return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
4960 }
4961
4962 void LevelStats_setPlayed(int nr, int value)
4963 {
4964   if (nr >= 0 && nr < MAX_LEVELS)
4965     level_stats[nr].played = value;
4966 }
4967
4968 void LevelStats_setSolved(int nr, int value)
4969 {
4970   if (nr >= 0 && nr < MAX_LEVELS)
4971     level_stats[nr].solved = value;
4972 }
4973
4974 void LevelStats_incPlayed(int nr)
4975 {
4976   if (nr >= 0 && nr < MAX_LEVELS)
4977     level_stats[nr].played++;
4978 }
4979
4980 void LevelStats_incSolved(int nr)
4981 {
4982   if (nr >= 0 && nr < MAX_LEVELS)
4983     level_stats[nr].solved++;
4984 }
4985
4986 void LoadUserSetup(void)
4987 {
4988   // --------------------------------------------------------------------------
4989   // ~/.<program>/usersetup.conf
4990   // --------------------------------------------------------------------------
4991
4992   char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
4993   SetupFileHash *user_setup_hash = NULL;
4994
4995   // always start with reliable default values
4996   user.nr = 0;
4997
4998   if ((user_setup_hash = loadSetupFileHash(filename)))
4999   {
5000     char *token_value;
5001
5002     // get last selected user number
5003     token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
5004
5005     if (token_value)
5006       user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
5007
5008     freeSetupFileHash(user_setup_hash);
5009   }
5010   else
5011   {
5012     Debug("setup", "using default setup values");
5013   }
5014
5015   free(filename);
5016 }
5017
5018 void SaveUserSetup(void)
5019 {
5020   // --------------------------------------------------------------------------
5021   // ~/.<program>/usersetup.conf
5022   // --------------------------------------------------------------------------
5023
5024   char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5025   FILE *file;
5026
5027   InitMainUserDataDirectory();
5028
5029   if (!(file = fopen(filename, MODE_WRITE)))
5030   {
5031     Warn("cannot write setup file '%s'", filename);
5032
5033     free(filename);
5034
5035     return;
5036   }
5037
5038   fprintFileHeader(file, USERSETUP_FILENAME);
5039
5040   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
5041                                                i_to_a(user.nr)));
5042   fclose(file);
5043
5044   SetFilePermissions(filename, PERMS_PRIVATE);
5045
5046   free(filename);
5047 }