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