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