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