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