fixed wording for custom artwork menu
[rocksndiamonds.git] / src / libgame / setup.c
1 // ============================================================================
2 // Artsoft Retro-Game Library
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
5 //                  Holger Schemel
6 //                  info@artsoft.org
7 //                  https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
9 // setup.c
10 // ============================================================================
11
12 #include <sys/types.h>
13 #include <sys/stat.h>
14 #include <dirent.h>
15 #include <string.h>
16 #include <unistd.h>
17 #include <errno.h>
18
19 #include "platform.h"
20
21 #include "setup.h"
22 #include "joystick.h"
23 #include "text.h"
24 #include "misc.h"
25 #include "hash.h"
26 #include "zip/miniunz.h"
27
28
29 #define ENABLE_UNUSED_CODE      FALSE   // for currently unused functions
30 #define DEBUG_NO_CONFIG_FILE    FALSE   // for extra-verbose debug output
31
32 #define NUM_LEVELCLASS_DESC     8
33
34 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
35 {
36   "Tutorial Levels",
37   "Classic Originals",
38   "Contributions",
39   "Private Levels",
40   "Boulderdash",
41   "Emerald Mine",
42   "Supaplex",
43   "DX Boulderdash"
44 };
45
46
47 #define LEVELCOLOR(n)   (IS_LEVELCLASS_TUTORIAL(n) ?            FC_BLUE :    \
48                          IS_LEVELCLASS_CLASSICS(n) ?            FC_RED :     \
49                          IS_LEVELCLASS_BD(n) ?                  FC_YELLOW :  \
50                          IS_LEVELCLASS_EM(n) ?                  FC_YELLOW :  \
51                          IS_LEVELCLASS_SP(n) ?                  FC_YELLOW :  \
52                          IS_LEVELCLASS_DX(n) ?                  FC_YELLOW :  \
53                          IS_LEVELCLASS_SB(n) ?                  FC_YELLOW :  \
54                          IS_LEVELCLASS_CONTRIB(n) ?             FC_GREEN :   \
55                          IS_LEVELCLASS_PRIVATE(n) ?             FC_RED :     \
56                          FC_BLUE)
57
58 #define LEVELSORTING(n) (IS_LEVELCLASS_TUTORIAL(n) ?            0 :     \
59                          IS_LEVELCLASS_CLASSICS(n) ?            1 :     \
60                          IS_LEVELCLASS_BD(n) ?                  2 :     \
61                          IS_LEVELCLASS_EM(n) ?                  3 :     \
62                          IS_LEVELCLASS_SP(n) ?                  4 :     \
63                          IS_LEVELCLASS_DX(n) ?                  5 :     \
64                          IS_LEVELCLASS_SB(n) ?                  6 :     \
65                          IS_LEVELCLASS_CONTRIB(n) ?             7 :     \
66                          IS_LEVELCLASS_PRIVATE(n) ?             8 :     \
67                          9)
68
69 #define ARTWORKCOLOR(n) (IS_ARTWORKCLASS_CLASSICS(n) ?          FC_RED :     \
70                          IS_ARTWORKCLASS_CONTRIB(n) ?           FC_GREEN :   \
71                          IS_ARTWORKCLASS_PRIVATE(n) ?           FC_RED :     \
72                          IS_ARTWORKCLASS_LEVEL(n) ?             FC_YELLOW :  \
73                          FC_BLUE)
74
75 #define ARTWORKSORTING(n) (IS_ARTWORKCLASS_CLASSICS(n) ?        0 :     \
76                            IS_ARTWORKCLASS_LEVEL(n) ?           1 :     \
77                            IS_ARTWORKCLASS_CONTRIB(n) ?         2 :     \
78                            IS_ARTWORKCLASS_PRIVATE(n) ?         3 :     \
79                            9)
80
81 #define TOKEN_VALUE_POSITION_SHORT              32
82 #define TOKEN_VALUE_POSITION_DEFAULT            40
83 #define TOKEN_COMMENT_POSITION_DEFAULT          60
84
85 #define MAX_COOKIE_LEN                          256
86
87
88 static void setTreeInfoToDefaults(TreeInfo *, int);
89 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
90 static int compareTreeInfoEntries(const void *, const void *);
91
92 static int token_value_position   = TOKEN_VALUE_POSITION_DEFAULT;
93 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
94
95 static SetupFileHash *artworkinfo_cache_old = NULL;
96 static SetupFileHash *artworkinfo_cache_new = NULL;
97 static SetupFileHash *optional_tokens_hash = NULL;
98 static boolean use_artworkinfo_cache = TRUE;
99 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 int compareSetupFileData(const void *object1, const void *object2)
2330 {
2331   const struct ConfigInfo *entry1 = (struct ConfigInfo *)object1;
2332   const struct ConfigInfo *entry2 = (struct ConfigInfo *)object2;
2333
2334   return strcmp(entry1->token, entry2->token);
2335 }
2336
2337 static void saveSetupFileHash(SetupFileHash *hash, char *filename)
2338 {
2339   int item_count = hashtable_count(hash);
2340   int item_size = sizeof(struct ConfigInfo);
2341   struct ConfigInfo *sort_array = checked_malloc(item_count * item_size);
2342   FILE *file;
2343   int i = 0;
2344
2345   // copy string pointers from hash to array
2346   BEGIN_HASH_ITERATION(hash, itr)
2347   {
2348     sort_array[i].token = HASH_ITERATION_TOKEN(itr);
2349     sort_array[i].value = HASH_ITERATION_VALUE(itr);
2350
2351     i++;
2352
2353     if (i > item_count)         // should never happen
2354       break;
2355   }
2356   END_HASH_ITERATION(hash, itr)
2357
2358   // sort string pointers from hash in array
2359   qsort(sort_array, item_count, item_size, compareSetupFileData);
2360
2361   if (!(file = fopen(filename, MODE_WRITE)))
2362   {
2363     Warn("cannot write configuration file '%s'", filename);
2364
2365     return;
2366   }
2367
2368   for (i = 0; i < item_count; i++)
2369     fprintf(file, "%s\n", getFormattedSetupEntry(sort_array[i].token,
2370                                                  sort_array[i].value));
2371   fclose(file);
2372
2373   checked_free(sort_array);
2374 }
2375
2376 SetupFileList *loadSetupFileList(char *filename)
2377 {
2378   SetupFileList *setup_file_list = newSetupFileList("", "");
2379   SetupFileList *first_valid_list_entry;
2380
2381   if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2382   {
2383     freeSetupFileList(setup_file_list);
2384
2385     return NULL;
2386   }
2387
2388   first_valid_list_entry = setup_file_list->next;
2389
2390   // free empty list header
2391   setup_file_list->next = NULL;
2392   freeSetupFileList(setup_file_list);
2393
2394   return first_valid_list_entry;
2395 }
2396
2397 SetupFileHash *loadSetupFileHash(char *filename)
2398 {
2399   SetupFileHash *setup_file_hash = newSetupFileHash();
2400
2401   if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2402   {
2403     freeSetupFileHash(setup_file_hash);
2404
2405     return NULL;
2406   }
2407
2408   return setup_file_hash;
2409 }
2410
2411
2412 // ============================================================================
2413 // setup file stuff
2414 // ============================================================================
2415
2416 #define TOKEN_STR_LAST_LEVEL_SERIES             "last_level_series"
2417 #define TOKEN_STR_LAST_PLAYED_LEVEL             "last_played_level"
2418 #define TOKEN_STR_HANDICAP_LEVEL                "handicap_level"
2419 #define TOKEN_STR_LAST_USER                     "last_user"
2420
2421 // level directory info
2422 #define LEVELINFO_TOKEN_IDENTIFIER              0
2423 #define LEVELINFO_TOKEN_NAME                    1
2424 #define LEVELINFO_TOKEN_NAME_SORTING            2
2425 #define LEVELINFO_TOKEN_AUTHOR                  3
2426 #define LEVELINFO_TOKEN_YEAR                    4
2427 #define LEVELINFO_TOKEN_PROGRAM_TITLE           5
2428 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT       6
2429 #define LEVELINFO_TOKEN_PROGRAM_COMPANY         7
2430 #define LEVELINFO_TOKEN_IMPORTED_FROM           8
2431 #define LEVELINFO_TOKEN_IMPORTED_BY             9
2432 #define LEVELINFO_TOKEN_TESTED_BY               10
2433 #define LEVELINFO_TOKEN_LEVELS                  11
2434 #define LEVELINFO_TOKEN_FIRST_LEVEL             12
2435 #define LEVELINFO_TOKEN_SORT_PRIORITY           13
2436 #define LEVELINFO_TOKEN_LATEST_ENGINE           14
2437 #define LEVELINFO_TOKEN_LEVEL_GROUP             15
2438 #define LEVELINFO_TOKEN_READONLY                16
2439 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS        17
2440 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA        18
2441 #define LEVELINFO_TOKEN_GRAPHICS_SET            19
2442 #define LEVELINFO_TOKEN_SOUNDS_SET_DEFAULT      20
2443 #define LEVELINFO_TOKEN_SOUNDS_SET_LOWPASS      21
2444 #define LEVELINFO_TOKEN_SOUNDS_SET              22
2445 #define LEVELINFO_TOKEN_MUSIC_SET               23
2446 #define LEVELINFO_TOKEN_FILENAME                24
2447 #define LEVELINFO_TOKEN_FILETYPE                25
2448 #define LEVELINFO_TOKEN_SPECIAL_FLAGS           26
2449 #define LEVELINFO_TOKEN_HANDICAP                27
2450 #define LEVELINFO_TOKEN_SKIP_LEVELS             28
2451 #define LEVELINFO_TOKEN_USE_EMC_TILES           29
2452
2453 #define NUM_LEVELINFO_TOKENS                    30
2454
2455 static LevelDirTree ldi;
2456
2457 static struct TokenInfo levelinfo_tokens[] =
2458 {
2459   // level directory info
2460   { TYPE_STRING,        &ldi.identifier,        "identifier"            },
2461   { TYPE_STRING,        &ldi.name,              "name"                  },
2462   { TYPE_STRING,        &ldi.name_sorting,      "name_sorting"          },
2463   { TYPE_STRING,        &ldi.author,            "author"                },
2464   { TYPE_STRING,        &ldi.year,              "year"                  },
2465   { TYPE_STRING,        &ldi.program_title,     "program_title"         },
2466   { TYPE_STRING,        &ldi.program_copyright, "program_copyright"     },
2467   { TYPE_STRING,        &ldi.program_company,   "program_company"       },
2468   { TYPE_STRING,        &ldi.imported_from,     "imported_from"         },
2469   { TYPE_STRING,        &ldi.imported_by,       "imported_by"           },
2470   { TYPE_STRING,        &ldi.tested_by,         "tested_by"             },
2471   { TYPE_INTEGER,       &ldi.levels,            "levels"                },
2472   { TYPE_INTEGER,       &ldi.first_level,       "first_level"           },
2473   { TYPE_INTEGER,       &ldi.sort_priority,     "sort_priority"         },
2474   { TYPE_BOOLEAN,       &ldi.latest_engine,     "latest_engine"         },
2475   { TYPE_BOOLEAN,       &ldi.level_group,       "level_group"           },
2476   { TYPE_BOOLEAN,       &ldi.readonly,          "readonly"              },
2477   { TYPE_STRING,        &ldi.graphics_set_ecs,  "graphics_set.ecs"      },
2478   { TYPE_STRING,        &ldi.graphics_set_aga,  "graphics_set.aga"      },
2479   { TYPE_STRING,        &ldi.graphics_set,      "graphics_set"          },
2480   { TYPE_STRING,        &ldi.sounds_set_default,"sounds_set.default"    },
2481   { TYPE_STRING,        &ldi.sounds_set_lowpass,"sounds_set.lowpass"    },
2482   { TYPE_STRING,        &ldi.sounds_set,        "sounds_set"            },
2483   { TYPE_STRING,        &ldi.music_set,         "music_set"             },
2484   { TYPE_STRING,        &ldi.level_filename,    "filename"              },
2485   { TYPE_STRING,        &ldi.level_filetype,    "filetype"              },
2486   { TYPE_STRING,        &ldi.special_flags,     "special_flags"         },
2487   { TYPE_BOOLEAN,       &ldi.handicap,          "handicap"              },
2488   { TYPE_BOOLEAN,       &ldi.skip_levels,       "skip_levels"           },
2489   { TYPE_BOOLEAN,       &ldi.use_emc_tiles,     "use_emc_tiles"         }
2490 };
2491
2492 static struct TokenInfo artworkinfo_tokens[] =
2493 {
2494   // artwork directory info
2495   { TYPE_STRING,        &ldi.identifier,        "identifier"            },
2496   { TYPE_STRING,        &ldi.subdir,            "subdir"                },
2497   { TYPE_STRING,        &ldi.name,              "name"                  },
2498   { TYPE_STRING,        &ldi.name_sorting,      "name_sorting"          },
2499   { TYPE_STRING,        &ldi.author,            "author"                },
2500   { TYPE_STRING,        &ldi.program_title,     "program_title"         },
2501   { TYPE_STRING,        &ldi.program_copyright, "program_copyright"     },
2502   { TYPE_STRING,        &ldi.program_company,   "program_company"       },
2503   { TYPE_INTEGER,       &ldi.sort_priority,     "sort_priority"         },
2504   { TYPE_STRING,        &ldi.basepath,          "basepath"              },
2505   { TYPE_STRING,        &ldi.fullpath,          "fullpath"              },
2506   { TYPE_BOOLEAN,       &ldi.in_user_dir,       "in_user_dir"           },
2507   { TYPE_INTEGER,       &ldi.color,             "color"                 },
2508   { TYPE_STRING,        &ldi.class_desc,        "class_desc"            },
2509
2510   { -1,                 NULL,                   NULL                    },
2511 };
2512
2513 static char *optional_tokens[] =
2514 {
2515   "program_title",
2516   "program_copyright",
2517   "program_company",
2518
2519   NULL
2520 };
2521
2522 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2523 {
2524   ti->type = type;
2525
2526   ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR    ? &leveldir_first :
2527                   ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2528                   ti->type == TREE_TYPE_SOUNDS_DIR   ? &artwork.snd_first :
2529                   ti->type == TREE_TYPE_MUSIC_DIR    ? &artwork.mus_first :
2530                   NULL);
2531
2532   ti->node_parent = NULL;
2533   ti->node_group = NULL;
2534   ti->next = NULL;
2535
2536   ti->cl_first = -1;
2537   ti->cl_cursor = -1;
2538
2539   ti->subdir = NULL;
2540   ti->fullpath = NULL;
2541   ti->basepath = NULL;
2542   ti->identifier = NULL;
2543   ti->name = getStringCopy(ANONYMOUS_NAME);
2544   ti->name_sorting = NULL;
2545   ti->author = getStringCopy(ANONYMOUS_NAME);
2546   ti->year = NULL;
2547
2548   ti->program_title = NULL;
2549   ti->program_copyright = NULL;
2550   ti->program_company = NULL;
2551
2552   ti->sort_priority = LEVELCLASS_UNDEFINED;     // default: least priority
2553   ti->latest_engine = FALSE;                    // default: get from level
2554   ti->parent_link = FALSE;
2555   ti->in_user_dir = FALSE;
2556   ti->user_defined = FALSE;
2557   ti->color = 0;
2558   ti->class_desc = NULL;
2559
2560   ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2561
2562   if (ti->type == TREE_TYPE_LEVEL_DIR)
2563   {
2564     ti->imported_from = NULL;
2565     ti->imported_by = NULL;
2566     ti->tested_by = NULL;
2567
2568     ti->graphics_set_ecs = NULL;
2569     ti->graphics_set_aga = NULL;
2570     ti->graphics_set = NULL;
2571     ti->sounds_set_default = NULL;
2572     ti->sounds_set_lowpass = NULL;
2573     ti->sounds_set = NULL;
2574     ti->music_set = NULL;
2575     ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2576     ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2577     ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2578
2579     ti->level_filename = NULL;
2580     ti->level_filetype = NULL;
2581
2582     ti->special_flags = NULL;
2583
2584     ti->levels = 0;
2585     ti->first_level = 0;
2586     ti->last_level = 0;
2587     ti->level_group = FALSE;
2588     ti->handicap_level = 0;
2589     ti->readonly = TRUE;
2590     ti->handicap = TRUE;
2591     ti->skip_levels = FALSE;
2592
2593     ti->use_emc_tiles = FALSE;
2594   }
2595 }
2596
2597 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2598 {
2599   if (parent == NULL)
2600   {
2601     Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
2602
2603     setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2604
2605     return;
2606   }
2607
2608   // copy all values from the parent structure
2609
2610   ti->type = parent->type;
2611
2612   ti->node_top = parent->node_top;
2613   ti->node_parent = parent;
2614   ti->node_group = NULL;
2615   ti->next = NULL;
2616
2617   ti->cl_first = -1;
2618   ti->cl_cursor = -1;
2619
2620   ti->subdir = NULL;
2621   ti->fullpath = NULL;
2622   ti->basepath = NULL;
2623   ti->identifier = NULL;
2624   ti->name = getStringCopy(ANONYMOUS_NAME);
2625   ti->name_sorting = NULL;
2626   ti->author = getStringCopy(parent->author);
2627   ti->year = getStringCopy(parent->year);
2628
2629   ti->program_title = getStringCopy(parent->program_title);
2630   ti->program_copyright = getStringCopy(parent->program_copyright);
2631   ti->program_company = getStringCopy(parent->program_company);
2632
2633   ti->sort_priority = parent->sort_priority;
2634   ti->latest_engine = parent->latest_engine;
2635   ti->parent_link = FALSE;
2636   ti->in_user_dir = parent->in_user_dir;
2637   ti->user_defined = parent->user_defined;
2638   ti->color = parent->color;
2639   ti->class_desc = getStringCopy(parent->class_desc);
2640
2641   ti->infotext = getStringCopy(parent->infotext);
2642
2643   if (ti->type == TREE_TYPE_LEVEL_DIR)
2644   {
2645     ti->imported_from = getStringCopy(parent->imported_from);
2646     ti->imported_by = getStringCopy(parent->imported_by);
2647     ti->tested_by = getStringCopy(parent->tested_by);
2648
2649     ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2650     ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2651     ti->graphics_set = getStringCopy(parent->graphics_set);
2652     ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
2653     ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
2654     ti->sounds_set = getStringCopy(parent->sounds_set);
2655     ti->music_set = getStringCopy(parent->music_set);
2656     ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2657     ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2658     ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2659
2660     ti->level_filename = getStringCopy(parent->level_filename);
2661     ti->level_filetype = getStringCopy(parent->level_filetype);
2662
2663     ti->special_flags = getStringCopy(parent->special_flags);
2664
2665     ti->levels = parent->levels;
2666     ti->first_level = parent->first_level;
2667     ti->last_level = parent->last_level;
2668     ti->level_group = FALSE;
2669     ti->handicap_level = parent->handicap_level;
2670     ti->readonly = parent->readonly;
2671     ti->handicap = parent->handicap;
2672     ti->skip_levels = parent->skip_levels;
2673
2674     ti->use_emc_tiles = parent->use_emc_tiles;
2675   }
2676 }
2677
2678 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2679 {
2680   TreeInfo *ti_copy = newTreeInfo();
2681
2682   // copy all values from the original structure
2683
2684   ti_copy->type                 = ti->type;
2685
2686   ti_copy->node_top             = ti->node_top;
2687   ti_copy->node_parent          = ti->node_parent;
2688   ti_copy->node_group           = ti->node_group;
2689   ti_copy->next                 = ti->next;
2690
2691   ti_copy->cl_first             = ti->cl_first;
2692   ti_copy->cl_cursor            = ti->cl_cursor;
2693
2694   ti_copy->subdir               = getStringCopy(ti->subdir);
2695   ti_copy->fullpath             = getStringCopy(ti->fullpath);
2696   ti_copy->basepath             = getStringCopy(ti->basepath);
2697   ti_copy->identifier           = getStringCopy(ti->identifier);
2698   ti_copy->name                 = getStringCopy(ti->name);
2699   ti_copy->name_sorting         = getStringCopy(ti->name_sorting);
2700   ti_copy->author               = getStringCopy(ti->author);
2701   ti_copy->year                 = getStringCopy(ti->year);
2702
2703   ti_copy->program_title        = getStringCopy(ti->program_title);
2704   ti_copy->program_copyright    = getStringCopy(ti->program_copyright);
2705   ti_copy->program_company      = getStringCopy(ti->program_company);
2706
2707   ti_copy->imported_from        = getStringCopy(ti->imported_from);
2708   ti_copy->imported_by          = getStringCopy(ti->imported_by);
2709   ti_copy->tested_by            = getStringCopy(ti->tested_by);
2710
2711   ti_copy->graphics_set_ecs     = getStringCopy(ti->graphics_set_ecs);
2712   ti_copy->graphics_set_aga     = getStringCopy(ti->graphics_set_aga);
2713   ti_copy->graphics_set         = getStringCopy(ti->graphics_set);
2714   ti_copy->sounds_set_default   = getStringCopy(ti->sounds_set_default);
2715   ti_copy->sounds_set_lowpass   = getStringCopy(ti->sounds_set_lowpass);
2716   ti_copy->sounds_set           = getStringCopy(ti->sounds_set);
2717   ti_copy->music_set            = getStringCopy(ti->music_set);
2718   ti_copy->graphics_path        = getStringCopy(ti->graphics_path);
2719   ti_copy->sounds_path          = getStringCopy(ti->sounds_path);
2720   ti_copy->music_path           = getStringCopy(ti->music_path);
2721
2722   ti_copy->level_filename       = getStringCopy(ti->level_filename);
2723   ti_copy->level_filetype       = getStringCopy(ti->level_filetype);
2724
2725   ti_copy->special_flags        = getStringCopy(ti->special_flags);
2726
2727   ti_copy->levels               = ti->levels;
2728   ti_copy->first_level          = ti->first_level;
2729   ti_copy->last_level           = ti->last_level;
2730   ti_copy->sort_priority        = ti->sort_priority;
2731
2732   ti_copy->latest_engine        = ti->latest_engine;
2733
2734   ti_copy->level_group          = ti->level_group;
2735   ti_copy->parent_link          = ti->parent_link;
2736   ti_copy->in_user_dir          = ti->in_user_dir;
2737   ti_copy->user_defined         = ti->user_defined;
2738   ti_copy->readonly             = ti->readonly;
2739   ti_copy->handicap             = ti->handicap;
2740   ti_copy->skip_levels          = ti->skip_levels;
2741
2742   ti_copy->use_emc_tiles        = ti->use_emc_tiles;
2743
2744   ti_copy->color                = ti->color;
2745   ti_copy->class_desc           = getStringCopy(ti->class_desc);
2746   ti_copy->handicap_level       = ti->handicap_level;
2747
2748   ti_copy->infotext             = getStringCopy(ti->infotext);
2749
2750   return ti_copy;
2751 }
2752
2753 void freeTreeInfo(TreeInfo *ti)
2754 {
2755   if (ti == NULL)
2756     return;
2757
2758   checked_free(ti->subdir);
2759   checked_free(ti->fullpath);
2760   checked_free(ti->basepath);
2761   checked_free(ti->identifier);
2762
2763   checked_free(ti->name);
2764   checked_free(ti->name_sorting);
2765   checked_free(ti->author);
2766   checked_free(ti->year);
2767
2768   checked_free(ti->program_title);
2769   checked_free(ti->program_copyright);
2770   checked_free(ti->program_company);
2771
2772   checked_free(ti->class_desc);
2773
2774   checked_free(ti->infotext);
2775
2776   if (ti->type == TREE_TYPE_LEVEL_DIR)
2777   {
2778     checked_free(ti->imported_from);
2779     checked_free(ti->imported_by);
2780     checked_free(ti->tested_by);
2781
2782     checked_free(ti->graphics_set_ecs);
2783     checked_free(ti->graphics_set_aga);
2784     checked_free(ti->graphics_set);
2785     checked_free(ti->sounds_set_default);
2786     checked_free(ti->sounds_set_lowpass);
2787     checked_free(ti->sounds_set);
2788     checked_free(ti->music_set);
2789
2790     checked_free(ti->graphics_path);
2791     checked_free(ti->sounds_path);
2792     checked_free(ti->music_path);
2793
2794     checked_free(ti->level_filename);
2795     checked_free(ti->level_filetype);
2796
2797     checked_free(ti->special_flags);
2798   }
2799
2800   // recursively free child node
2801   if (ti->node_group)
2802     freeTreeInfo(ti->node_group);
2803
2804   // recursively free next node
2805   if (ti->next)
2806     freeTreeInfo(ti->next);
2807
2808   checked_free(ti);
2809 }
2810
2811 void setSetupInfo(struct TokenInfo *token_info,
2812                   int token_nr, char *token_value)
2813 {
2814   int token_type = token_info[token_nr].type;
2815   void *setup_value = token_info[token_nr].value;
2816
2817   if (token_value == NULL)
2818     return;
2819
2820   // set setup field to corresponding token value
2821   switch (token_type)
2822   {
2823     case TYPE_BOOLEAN:
2824     case TYPE_SWITCH:
2825       *(boolean *)setup_value = get_boolean_from_string(token_value);
2826       break;
2827
2828     case TYPE_SWITCH3:
2829       *(int *)setup_value = get_switch3_from_string(token_value);
2830       break;
2831
2832     case TYPE_KEY:
2833       *(Key *)setup_value = getKeyFromKeyName(token_value);
2834       break;
2835
2836     case TYPE_KEY_X11:
2837       *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2838       break;
2839
2840     case TYPE_INTEGER:
2841       *(int *)setup_value = get_integer_from_string(token_value);
2842       break;
2843
2844     case TYPE_STRING:
2845       checked_free(*(char **)setup_value);
2846       *(char **)setup_value = getStringCopy(token_value);
2847       break;
2848
2849     case TYPE_PLAYER:
2850       *(int *)setup_value = get_player_nr_from_string(token_value);
2851       break;
2852
2853     default:
2854       break;
2855   }
2856 }
2857
2858 static int compareTreeInfoEntries(const void *object1, const void *object2)
2859 {
2860   const TreeInfo *entry1 = *((TreeInfo **)object1);
2861   const TreeInfo *entry2 = *((TreeInfo **)object2);
2862   int class_sorting1 = 0, class_sorting2 = 0;
2863   int compare_result;
2864
2865   if (entry1->type == TREE_TYPE_LEVEL_DIR)
2866   {
2867     class_sorting1 = LEVELSORTING(entry1);
2868     class_sorting2 = LEVELSORTING(entry2);
2869   }
2870   else if (entry1->type == TREE_TYPE_GRAPHICS_DIR ||
2871            entry1->type == TREE_TYPE_SOUNDS_DIR ||
2872            entry1->type == TREE_TYPE_MUSIC_DIR)
2873   {
2874     class_sorting1 = ARTWORKSORTING(entry1);
2875     class_sorting2 = ARTWORKSORTING(entry2);
2876   }
2877
2878   if (entry1->parent_link || entry2->parent_link)
2879     compare_result = (entry1->parent_link ? -1 : +1);
2880   else if (entry1->sort_priority == entry2->sort_priority)
2881   {
2882     char *name1 = getStringToLower(entry1->name_sorting);
2883     char *name2 = getStringToLower(entry2->name_sorting);
2884
2885     compare_result = strcmp(name1, name2);
2886
2887     free(name1);
2888     free(name2);
2889   }
2890   else if (class_sorting1 == class_sorting2)
2891     compare_result = entry1->sort_priority - entry2->sort_priority;
2892   else
2893     compare_result = class_sorting1 - class_sorting2;
2894
2895   return compare_result;
2896 }
2897
2898 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2899 {
2900   TreeInfo *ti_new;
2901
2902   if (node_parent == NULL)
2903     return NULL;
2904
2905   ti_new = newTreeInfo();
2906   setTreeInfoToDefaults(ti_new, node_parent->type);
2907
2908   ti_new->node_parent = node_parent;
2909   ti_new->parent_link = TRUE;
2910
2911   setString(&ti_new->identifier, node_parent->identifier);
2912   setString(&ti_new->name, BACKLINK_TEXT_PARENT);
2913   setString(&ti_new->name_sorting, ti_new->name);
2914
2915   setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2916   setString(&ti_new->fullpath, node_parent->fullpath);
2917
2918   ti_new->sort_priority = node_parent->sort_priority;
2919   ti_new->latest_engine = node_parent->latest_engine;
2920
2921   setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2922
2923   pushTreeInfo(&node_parent->node_group, ti_new);
2924
2925   return ti_new;
2926 }
2927
2928 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
2929 {
2930   if (node_first == NULL)
2931     return NULL;
2932
2933   TreeInfo *ti_new = newTreeInfo();
2934   int type = node_first->type;
2935
2936   setTreeInfoToDefaults(ti_new, type);
2937
2938   ti_new->node_parent = NULL;
2939   ti_new->parent_link = FALSE;
2940
2941   setString(&ti_new->identifier, node_first->identifier);
2942   setString(&ti_new->name, TREE_INFOTEXT(type));
2943   setString(&ti_new->name_sorting, ti_new->name);
2944
2945   setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
2946   setString(&ti_new->fullpath, ".");
2947
2948   ti_new->sort_priority = node_first->sort_priority;;
2949   ti_new->latest_engine = node_first->latest_engine;
2950
2951   setString(&ti_new->class_desc, TREE_INFOTEXT(type));
2952
2953   ti_new->node_group = node_first;
2954   ti_new->level_group = TRUE;
2955
2956   TreeInfo *ti_new2 = createParentTreeInfoNode(ti_new);
2957
2958   setString(&ti_new2->name, TREE_BACKLINK_TEXT(type));
2959   setString(&ti_new2->name_sorting, ti_new2->name);
2960
2961   return ti_new;
2962 }
2963
2964 static void setTreeInfoParentNodes(TreeInfo *node, TreeInfo *node_parent)
2965 {
2966   while (node)
2967   {
2968     if (node->node_group)
2969       setTreeInfoParentNodes(node->node_group, node);
2970
2971     node->node_parent = node_parent;
2972
2973     node = node->next;
2974   }
2975 }
2976
2977
2978 // ----------------------------------------------------------------------------
2979 // functions for handling level and custom artwork info cache
2980 // ----------------------------------------------------------------------------
2981
2982 static void LoadArtworkInfoCache(void)
2983 {
2984   InitCacheDirectory();
2985
2986   if (artworkinfo_cache_old == NULL)
2987   {
2988     char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2989
2990     // try to load artwork info hash from already existing cache file
2991     artworkinfo_cache_old = loadSetupFileHash(filename);
2992
2993     // if no artwork info cache file was found, start with empty hash
2994     if (artworkinfo_cache_old == NULL)
2995       artworkinfo_cache_old = newSetupFileHash();
2996
2997     free(filename);
2998   }
2999
3000   if (artworkinfo_cache_new == NULL)
3001     artworkinfo_cache_new = newSetupFileHash();
3002
3003   update_artworkinfo_cache = FALSE;
3004 }
3005
3006 static void SaveArtworkInfoCache(void)
3007 {
3008   if (!update_artworkinfo_cache)
3009     return;
3010
3011   char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3012
3013   InitCacheDirectory();
3014
3015   saveSetupFileHash(artworkinfo_cache_new, filename);
3016
3017   free(filename);
3018 }
3019
3020 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
3021 {
3022   static char *prefix = NULL;
3023
3024   checked_free(prefix);
3025
3026   prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
3027
3028   return prefix;
3029 }
3030
3031 // (identical to above function, but separate string buffer needed -- nasty)
3032 static char *getCacheToken(char *prefix, char *suffix)
3033 {
3034   static char *token = NULL;
3035
3036   checked_free(token);
3037
3038   token = getStringCat2WithSeparator(prefix, suffix, ".");
3039
3040   return token;
3041 }
3042
3043 static char *getFileTimestampString(char *filename)
3044 {
3045   return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
3046 }
3047
3048 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
3049 {
3050   struct stat file_status;
3051
3052   if (timestamp_string == NULL)
3053     return TRUE;
3054
3055   if (!fileExists(filename))                    // file does not exist
3056     return (atoi(timestamp_string) != 0);
3057
3058   if (stat(filename, &file_status) != 0)        // cannot stat file
3059     return TRUE;
3060
3061   return (file_status.st_mtime != atoi(timestamp_string));
3062 }
3063
3064 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3065 {
3066   char *identifier = level_node->subdir;
3067   char *type_string = ARTWORK_DIRECTORY(type);
3068   char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3069   char *token_main = getCacheToken(token_prefix, "CACHED");
3070   char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3071   boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3072   TreeInfo *artwork_info = NULL;
3073
3074   if (!use_artworkinfo_cache)
3075     return NULL;
3076
3077   if (optional_tokens_hash == NULL)
3078   {
3079     int i;
3080
3081     // create hash from list of optional tokens (for quick access)
3082     optional_tokens_hash = newSetupFileHash();
3083     for (i = 0; optional_tokens[i] != NULL; i++)
3084       setHashEntry(optional_tokens_hash, optional_tokens[i], "");
3085   }
3086
3087   if (cached)
3088   {
3089     int i;
3090
3091     artwork_info = newTreeInfo();
3092     setTreeInfoToDefaults(artwork_info, type);
3093
3094     // set all structure fields according to the token/value pairs
3095     ldi = *artwork_info;
3096     for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3097     {
3098       char *token_suffix = artworkinfo_tokens[i].text;
3099       char *token = getCacheToken(token_prefix, token_suffix);
3100       char *value = getHashEntry(artworkinfo_cache_old, token);
3101       boolean optional =
3102         (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
3103
3104       setSetupInfo(artworkinfo_tokens, i, value);
3105
3106       // check if cache entry for this item is mandatory, but missing
3107       if (value == NULL && !optional)
3108       {
3109         Warn("missing cache entry '%s'", token);
3110
3111         cached = FALSE;
3112       }
3113     }
3114
3115     *artwork_info = ldi;
3116   }
3117
3118   if (cached)
3119   {
3120     char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3121                                         LEVELINFO_FILENAME);
3122     char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3123                                           ARTWORKINFO_FILENAME(type));
3124
3125     // check if corresponding "levelinfo.conf" file has changed
3126     token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3127     cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3128
3129     if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3130       cached = FALSE;
3131
3132     // check if corresponding "<artworkinfo>.conf" file has changed
3133     token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3134     cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3135
3136     if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3137       cached = FALSE;
3138
3139     checked_free(filename_levelinfo);
3140     checked_free(filename_artworkinfo);
3141   }
3142
3143   if (!cached && artwork_info != NULL)
3144   {
3145     freeTreeInfo(artwork_info);
3146
3147     return NULL;
3148   }
3149
3150   return artwork_info;
3151 }
3152
3153 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3154                                      LevelDirTree *level_node, int type)
3155 {
3156   char *identifier = level_node->subdir;
3157   char *type_string = ARTWORK_DIRECTORY(type);
3158   char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3159   char *token_main = getCacheToken(token_prefix, "CACHED");
3160   boolean set_cache_timestamps = TRUE;
3161   int i;
3162
3163   setHashEntry(artworkinfo_cache_new, token_main, "true");
3164
3165   if (set_cache_timestamps)
3166   {
3167     char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3168                                         LEVELINFO_FILENAME);
3169     char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3170                                           ARTWORKINFO_FILENAME(type));
3171     char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3172     char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3173
3174     token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3175     setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3176
3177     token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3178     setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3179
3180     checked_free(filename_levelinfo);
3181     checked_free(filename_artworkinfo);
3182     checked_free(timestamp_levelinfo);
3183     checked_free(timestamp_artworkinfo);
3184   }
3185
3186   ldi = *artwork_info;
3187   for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3188   {
3189     char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3190     char *value = getSetupValue(artworkinfo_tokens[i].type,
3191                                 artworkinfo_tokens[i].value);
3192     if (value != NULL)
3193       setHashEntry(artworkinfo_cache_new, token, value);
3194   }
3195 }
3196
3197
3198 // ----------------------------------------------------------------------------
3199 // functions for loading level info and custom artwork info
3200 // ----------------------------------------------------------------------------
3201
3202 int GetZipFileTreeType(char *zip_filename)
3203 {
3204   static char *top_dir_path = NULL;
3205   static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3206   static char *conf_basename[NUM_BASE_TREE_TYPES] =
3207   {
3208     GRAPHICSINFO_FILENAME,
3209     SOUNDSINFO_FILENAME,
3210     MUSICINFO_FILENAME,
3211     LEVELINFO_FILENAME
3212   };
3213   int j;
3214
3215   checked_free(top_dir_path);
3216   top_dir_path = NULL;
3217
3218   for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3219   {
3220     checked_free(top_dir_conf_filename[j]);
3221     top_dir_conf_filename[j] = NULL;
3222   }
3223
3224   char **zip_entries = zip_list(zip_filename);
3225
3226   // check if zip file successfully opened
3227   if (zip_entries == NULL || zip_entries[0] == NULL)
3228     return TREE_TYPE_UNDEFINED;
3229
3230   // first zip file entry is expected to be top level directory
3231   char *top_dir = zip_entries[0];
3232
3233   // check if valid top level directory found in zip file
3234   if (!strSuffix(top_dir, "/"))
3235     return TREE_TYPE_UNDEFINED;
3236
3237   // get filenames of valid configuration files in top level directory
3238   for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3239     top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3240
3241   int tree_type = TREE_TYPE_UNDEFINED;
3242   int e = 0;
3243
3244   while (zip_entries[e] != NULL)
3245   {
3246     // check if every zip file entry is below top level directory
3247     if (!strPrefix(zip_entries[e], top_dir))
3248       return TREE_TYPE_UNDEFINED;
3249
3250     // check if this zip file entry is a valid configuration filename
3251     for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3252     {
3253       if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3254       {
3255         // only exactly one valid configuration file allowed
3256         if (tree_type != TREE_TYPE_UNDEFINED)
3257           return TREE_TYPE_UNDEFINED;
3258
3259         tree_type = j;
3260       }
3261     }
3262
3263     e++;
3264   }
3265
3266   return tree_type;
3267 }
3268
3269 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3270                                         int tree_type)
3271 {
3272   static char *top_dir_path = NULL;
3273   static char *top_dir_conf_filename = NULL;
3274
3275   checked_free(top_dir_path);
3276   checked_free(top_dir_conf_filename);
3277
3278   top_dir_path = NULL;
3279   top_dir_conf_filename = NULL;
3280
3281   char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3282                          ARTWORKINFO_FILENAME(tree_type));
3283
3284   // check if valid configuration filename determined
3285   if (conf_basename == NULL || strEqual(conf_basename, ""))
3286     return FALSE;
3287
3288   char **zip_entries = zip_list(zip_filename);
3289
3290   // check if zip file successfully opened
3291   if (zip_entries == NULL || zip_entries[0] == NULL)
3292     return FALSE;
3293
3294   // first zip file entry is expected to be top level directory
3295   char *top_dir = zip_entries[0];
3296
3297   // check if valid top level directory found in zip file
3298   if (!strSuffix(top_dir, "/"))
3299     return FALSE;
3300
3301   // get path of extracted top level directory
3302   top_dir_path = getPath2(directory, top_dir);
3303
3304   // remove trailing directory separator from top level directory path
3305   // (required to be able to check for file and directory in next step)
3306   top_dir_path[strlen(top_dir_path) - 1] = '\0';
3307
3308   // check if zip file's top level directory already exists in target directory
3309   if (fileExists(top_dir_path))         // (checks for file and directory)
3310     return FALSE;
3311
3312   // get filename of configuration file in top level directory
3313   top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3314
3315   boolean found_top_dir_conf_filename = FALSE;
3316   int i = 0;
3317
3318   while (zip_entries[i] != NULL)
3319   {
3320     // check if every zip file entry is below top level directory
3321     if (!strPrefix(zip_entries[i], top_dir))
3322       return FALSE;
3323
3324     // check if this zip file entry is the configuration filename
3325     if (strEqual(zip_entries[i], top_dir_conf_filename))
3326       found_top_dir_conf_filename = TRUE;
3327
3328     i++;
3329   }
3330
3331   // check if valid configuration filename was found in zip file
3332   if (!found_top_dir_conf_filename)
3333     return FALSE;
3334
3335   return TRUE;
3336 }
3337
3338 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3339                                   int tree_type)
3340 {
3341   boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3342                                                     tree_type);
3343
3344   if (!zip_file_valid)
3345   {
3346     Warn("zip file '%s' rejected!", zip_filename);
3347
3348     return NULL;
3349   }
3350
3351   char **zip_entries = zip_extract(zip_filename, directory);
3352
3353   if (zip_entries == NULL)
3354   {
3355     Warn("zip file '%s' could not be extracted!", zip_filename);
3356
3357     return NULL;
3358   }
3359
3360   Info("zip file '%s' successfully extracted!", zip_filename);
3361
3362   // first zip file entry contains top level directory
3363   char *top_dir = zip_entries[0];
3364
3365   // remove trailing directory separator from top level directory
3366   top_dir[strlen(top_dir) - 1] = '\0';
3367
3368   return top_dir;
3369 }
3370
3371 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3372 {
3373   Directory *dir;
3374   DirectoryEntry *dir_entry;
3375
3376   if ((dir = openDirectory(directory)) == NULL)
3377   {
3378     // display error if directory is main "options.graphics_directory" etc.
3379     if (tree_type == TREE_TYPE_LEVEL_DIR ||
3380         directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3381       Warn("cannot read directory '%s'", directory);
3382
3383     return;
3384   }
3385
3386   while ((dir_entry = readDirectory(dir)) != NULL)      // loop all entries
3387   {
3388     // skip non-zip files (and also directories with zip extension)
3389     if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3390       continue;
3391
3392     char *zip_filename = getPath2(directory, dir_entry->basename);
3393     char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3394     char *zip_filename_rejected  = getStringCat2(zip_filename, ".rejected");
3395
3396     // check if zip file hasn't already been extracted or rejected
3397     if (!fileExists(zip_filename_extracted) &&
3398         !fileExists(zip_filename_rejected))
3399     {
3400       char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3401                                                   tree_type);
3402       char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3403                                zip_filename_rejected);
3404       FILE *marker_file;
3405
3406       // create empty file to mark zip file as extracted or rejected
3407       if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3408         fclose(marker_file);
3409
3410       free(zip_filename);
3411       free(zip_filename_extracted);
3412       free(zip_filename_rejected);
3413     }
3414   }
3415
3416   closeDirectory(dir);
3417 }
3418
3419 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3420 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3421
3422 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3423                                           TreeInfo *node_parent,
3424                                           char *level_directory,
3425                                           char *directory_name)
3426 {
3427   char *directory_path = getPath2(level_directory, directory_name);
3428   char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3429   SetupFileHash *setup_file_hash;
3430   LevelDirTree *leveldir_new = NULL;
3431   int i;
3432
3433   // unless debugging, silently ignore directories without "levelinfo.conf"
3434   if (!options.debug && !fileExists(filename))
3435   {
3436     free(directory_path);
3437     free(filename);
3438
3439     return FALSE;
3440   }
3441
3442   setup_file_hash = loadSetupFileHash(filename);
3443
3444   if (setup_file_hash == NULL)
3445   {
3446 #if DEBUG_NO_CONFIG_FILE
3447     Debug("setup", "ignoring level directory '%s'", directory_path);
3448 #endif
3449
3450     free(directory_path);
3451     free(filename);
3452
3453     return FALSE;
3454   }
3455
3456   leveldir_new = newTreeInfo();
3457
3458   if (node_parent)
3459     setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3460   else
3461     setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3462
3463   leveldir_new->subdir = getStringCopy(directory_name);
3464
3465   // set all structure fields according to the token/value pairs
3466   ldi = *leveldir_new;
3467   for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3468     setSetupInfo(levelinfo_tokens, i,
3469                  getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3470   *leveldir_new = ldi;
3471
3472   if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3473     setString(&leveldir_new->name, leveldir_new->subdir);
3474
3475   if (leveldir_new->identifier == NULL)
3476     leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3477
3478   if (leveldir_new->name_sorting == NULL)
3479     leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3480
3481   if (node_parent == NULL)              // top level group
3482   {
3483     leveldir_new->basepath = getStringCopy(level_directory);
3484     leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3485   }
3486   else                                  // sub level group
3487   {
3488     leveldir_new->basepath = getStringCopy(node_parent->basepath);
3489     leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3490   }
3491
3492   leveldir_new->last_level =
3493     leveldir_new->first_level + leveldir_new->levels - 1;
3494
3495   leveldir_new->in_user_dir =
3496     (!strEqual(leveldir_new->basepath, options.level_directory));
3497
3498   // adjust some settings if user's private level directory was detected
3499   if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3500       leveldir_new->in_user_dir &&
3501       (strEqual(leveldir_new->subdir, getLoginName()) ||
3502        strEqual(leveldir_new->name,   getLoginName()) ||
3503        strEqual(leveldir_new->author, getRealName())))
3504   {
3505     leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3506     leveldir_new->readonly = FALSE;
3507   }
3508
3509   leveldir_new->user_defined =
3510     (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3511
3512   leveldir_new->color = LEVELCOLOR(leveldir_new);
3513
3514   setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3515
3516   leveldir_new->handicap_level =        // set handicap to default value
3517     (leveldir_new->user_defined || !leveldir_new->handicap ?
3518      leveldir_new->last_level : leveldir_new->first_level);
3519
3520   DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3521
3522   pushTreeInfo(node_first, leveldir_new);
3523
3524   freeSetupFileHash(setup_file_hash);
3525
3526   if (leveldir_new->level_group)
3527   {
3528     // create node to link back to current level directory
3529     createParentTreeInfoNode(leveldir_new);
3530
3531     // recursively step into sub-directory and look for more level series
3532     LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3533                               leveldir_new, directory_path);
3534   }
3535
3536   free(directory_path);
3537   free(filename);
3538
3539   return TRUE;
3540 }
3541
3542 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3543                                       TreeInfo *node_parent,
3544                                       char *level_directory)
3545 {
3546   // ---------- 1st stage: process any level set zip files ----------
3547
3548   ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3549
3550   // ---------- 2nd stage: check for level set directories ----------
3551
3552   Directory *dir;
3553   DirectoryEntry *dir_entry;
3554   boolean valid_entry_found = FALSE;
3555
3556   if ((dir = openDirectory(level_directory)) == NULL)
3557   {
3558     Warn("cannot read level directory '%s'", level_directory);
3559
3560     return;
3561   }
3562
3563   while ((dir_entry = readDirectory(dir)) != NULL)      // loop all entries
3564   {
3565     char *directory_name = dir_entry->basename;
3566     char *directory_path = getPath2(level_directory, directory_name);
3567
3568     // skip entries for current and parent directory
3569     if (strEqual(directory_name, ".") ||
3570         strEqual(directory_name, ".."))
3571     {
3572       free(directory_path);
3573
3574       continue;
3575     }
3576
3577     // find out if directory entry is itself a directory
3578     if (!dir_entry->is_directory)                       // not a directory
3579     {
3580       free(directory_path);
3581
3582       continue;
3583     }
3584
3585     free(directory_path);
3586
3587     if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3588         strEqual(directory_name, SOUNDS_DIRECTORY) ||
3589         strEqual(directory_name, MUSIC_DIRECTORY))
3590       continue;
3591
3592     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3593                                                     level_directory,
3594                                                     directory_name);
3595   }
3596
3597   closeDirectory(dir);
3598
3599   // special case: top level directory may directly contain "levelinfo.conf"
3600   if (node_parent == NULL && !valid_entry_found)
3601   {
3602     // check if this directory directly contains a file "levelinfo.conf"
3603     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3604                                                     level_directory, ".");
3605   }
3606
3607   if (!valid_entry_found)
3608     Warn("cannot find any valid level series in directory '%s'",
3609           level_directory);
3610 }
3611
3612 boolean AdjustGraphicsForEMC(void)
3613 {
3614   boolean settings_changed = FALSE;
3615
3616   settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3617   settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3618
3619   return settings_changed;
3620 }
3621
3622 boolean AdjustSoundsForEMC(void)
3623 {
3624   boolean settings_changed = FALSE;
3625
3626   settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
3627   settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
3628
3629   return settings_changed;
3630 }
3631
3632 void LoadLevelInfo(void)
3633 {
3634   InitUserLevelDirectory(getLoginName());
3635
3636   DrawInitText("Loading level series", 120, FC_GREEN);
3637
3638   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3639   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3640
3641   leveldir_first = createTopTreeInfoNode(leveldir_first);
3642
3643   /* after loading all level set information, clone the level directory tree
3644      and remove all level sets without levels (these may still contain artwork
3645      to be offered in the setup menu as "custom artwork", and are therefore
3646      checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3647   leveldir_first_all = leveldir_first;
3648   cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3649
3650   AdjustGraphicsForEMC();
3651   AdjustSoundsForEMC();
3652
3653   // before sorting, the first entries will be from the user directory
3654   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3655
3656   if (leveldir_first == NULL)
3657     Fail("cannot find any valid level series in any directory");
3658
3659   sortTreeInfo(&leveldir_first);
3660
3661 #if ENABLE_UNUSED_CODE
3662   dumpTreeInfo(leveldir_first, 0);
3663 #endif
3664 }
3665
3666 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3667                                               TreeInfo *node_parent,
3668                                               char *base_directory,
3669                                               char *directory_name, int type)
3670 {
3671   char *directory_path = getPath2(base_directory, directory_name);
3672   char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3673   SetupFileHash *setup_file_hash = NULL;
3674   TreeInfo *artwork_new = NULL;
3675   int i;
3676
3677   if (fileExists(filename))
3678     setup_file_hash = loadSetupFileHash(filename);
3679
3680   if (setup_file_hash == NULL)  // no config file -- look for artwork files
3681   {
3682     Directory *dir;
3683     DirectoryEntry *dir_entry;
3684     boolean valid_file_found = FALSE;
3685
3686     if ((dir = openDirectory(directory_path)) != NULL)
3687     {
3688       while ((dir_entry = readDirectory(dir)) != NULL)
3689       {
3690         if (FileIsArtworkType(dir_entry->filename, type))
3691         {
3692           valid_file_found = TRUE;
3693
3694           break;
3695         }
3696       }
3697
3698       closeDirectory(dir);
3699     }
3700
3701     if (!valid_file_found)
3702     {
3703 #if DEBUG_NO_CONFIG_FILE
3704       if (!strEqual(directory_name, "."))
3705         Debug("setup", "ignoring artwork directory '%s'", directory_path);
3706 #endif
3707
3708       free(directory_path);
3709       free(filename);
3710
3711       return FALSE;
3712     }
3713   }
3714
3715   artwork_new = newTreeInfo();
3716
3717   if (node_parent)
3718     setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3719   else
3720     setTreeInfoToDefaults(artwork_new, type);
3721
3722   artwork_new->subdir = getStringCopy(directory_name);
3723
3724   if (setup_file_hash)  // (before defining ".color" and ".class_desc")
3725   {
3726     // set all structure fields according to the token/value pairs
3727     ldi = *artwork_new;
3728     for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3729       setSetupInfo(levelinfo_tokens, i,
3730                    getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3731     *artwork_new = ldi;
3732
3733     if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3734       setString(&artwork_new->name, artwork_new->subdir);
3735
3736     if (artwork_new->identifier == NULL)
3737       artwork_new->identifier = getStringCopy(artwork_new->subdir);
3738
3739     if (artwork_new->name_sorting == NULL)
3740       artwork_new->name_sorting = getStringCopy(artwork_new->name);
3741   }
3742
3743   if (node_parent == NULL)              // top level group
3744   {
3745     artwork_new->basepath = getStringCopy(base_directory);
3746     artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3747   }
3748   else                                  // sub level group
3749   {
3750     artwork_new->basepath = getStringCopy(node_parent->basepath);
3751     artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3752   }
3753
3754   artwork_new->in_user_dir =
3755     (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3756
3757   // (may use ".sort_priority" from "setup_file_hash" above)
3758   artwork_new->color = ARTWORKCOLOR(artwork_new);
3759
3760   setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3761
3762   if (setup_file_hash == NULL)  // (after determining ".user_defined")
3763   {
3764     if (strEqual(artwork_new->subdir, "."))
3765     {
3766       if (artwork_new->user_defined)
3767       {
3768         setString(&artwork_new->identifier, "private");
3769         artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3770       }
3771       else
3772       {
3773         setString(&artwork_new->identifier, "classic");
3774         artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3775       }
3776
3777       // set to new values after changing ".sort_priority"
3778       artwork_new->color = ARTWORKCOLOR(artwork_new);
3779
3780       setString(&artwork_new->class_desc,
3781                 getLevelClassDescription(artwork_new));
3782     }
3783     else
3784     {
3785       setString(&artwork_new->identifier, artwork_new->subdir);
3786     }
3787
3788     setString(&artwork_new->name, artwork_new->identifier);
3789     setString(&artwork_new->name_sorting, artwork_new->name);
3790   }
3791
3792   pushTreeInfo(node_first, artwork_new);
3793
3794   freeSetupFileHash(setup_file_hash);
3795
3796   free(directory_path);
3797   free(filename);
3798
3799   return TRUE;
3800 }
3801
3802 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3803                                           TreeInfo *node_parent,
3804                                           char *base_directory, int type)
3805 {
3806   // ---------- 1st stage: process any artwork set zip files ----------
3807
3808   ProcessZipFilesInDirectory(base_directory, type);
3809
3810   // ---------- 2nd stage: check for artwork set directories ----------
3811
3812   Directory *dir;
3813   DirectoryEntry *dir_entry;
3814   boolean valid_entry_found = FALSE;
3815
3816   if ((dir = openDirectory(base_directory)) == NULL)
3817   {
3818     // display error if directory is main "options.graphics_directory" etc.
3819     if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3820       Warn("cannot read directory '%s'", base_directory);
3821
3822     return;
3823   }
3824
3825   while ((dir_entry = readDirectory(dir)) != NULL)      // loop all entries
3826   {
3827     char *directory_name = dir_entry->basename;
3828     char *directory_path = getPath2(base_directory, directory_name);
3829
3830     // skip directory entries for current and parent directory
3831     if (strEqual(directory_name, ".") ||
3832         strEqual(directory_name, ".."))
3833     {
3834       free(directory_path);
3835
3836       continue;
3837     }
3838
3839     // skip directory entries which are not a directory
3840     if (!dir_entry->is_directory)                       // not a directory
3841     {
3842       free(directory_path);
3843
3844       continue;
3845     }
3846
3847     free(directory_path);
3848
3849     // check if this directory contains artwork with or without config file
3850     valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3851                                                         base_directory,
3852                                                         directory_name, type);
3853   }
3854
3855   closeDirectory(dir);
3856
3857   // check if this directory directly contains artwork itself
3858   valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3859                                                       base_directory, ".",
3860                                                       type);
3861   if (!valid_entry_found)
3862     Warn("cannot find any valid artwork in directory '%s'", base_directory);
3863 }
3864
3865 static TreeInfo *getDummyArtworkInfo(int type)
3866 {
3867   // this is only needed when there is completely no artwork available
3868   TreeInfo *artwork_new = newTreeInfo();
3869
3870   setTreeInfoToDefaults(artwork_new, type);
3871
3872   setString(&artwork_new->subdir,   UNDEFINED_FILENAME);
3873   setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3874   setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3875
3876   setString(&artwork_new->identifier,   UNDEFINED_FILENAME);
3877   setString(&artwork_new->name,         UNDEFINED_FILENAME);
3878   setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3879
3880   return artwork_new;
3881 }
3882
3883 void SetCurrentArtwork(int type)
3884 {
3885   ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
3886   ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
3887   char *setup_set = SETUP_ARTWORK_SET(setup, type);
3888   char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
3889
3890   // set current artwork to artwork configured in setup menu
3891   *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
3892
3893   // if not found, set current artwork to default artwork
3894   if (*current_ptr == NULL)
3895     *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
3896
3897   // if not found, set current artwork to first artwork in tree
3898   if (*current_ptr == NULL)
3899     *current_ptr = getFirstValidTreeInfoEntry(first_node);
3900 }
3901
3902 void ChangeCurrentArtworkIfNeeded(int type)
3903 {
3904   char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
3905   char *setup_set = SETUP_ARTWORK_SET(setup, type);
3906
3907   if (!strEqual(current_identifier, setup_set))
3908     SetCurrentArtwork(type);
3909 }
3910
3911 void LoadArtworkInfo(void)
3912 {
3913   LoadArtworkInfoCache();
3914
3915   DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3916
3917   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3918                                 options.graphics_directory,
3919                                 TREE_TYPE_GRAPHICS_DIR);
3920   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3921                                 getUserGraphicsDir(),
3922                                 TREE_TYPE_GRAPHICS_DIR);
3923
3924   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3925                                 options.sounds_directory,
3926                                 TREE_TYPE_SOUNDS_DIR);
3927   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3928                                 getUserSoundsDir(),
3929                                 TREE_TYPE_SOUNDS_DIR);
3930
3931   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3932                                 options.music_directory,
3933                                 TREE_TYPE_MUSIC_DIR);
3934   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3935                                 getUserMusicDir(),
3936                                 TREE_TYPE_MUSIC_DIR);
3937
3938   if (artwork.gfx_first == NULL)
3939     artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3940   if (artwork.snd_first == NULL)
3941     artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3942   if (artwork.mus_first == NULL)
3943     artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3944
3945   // before sorting, the first entries will be from the user directory
3946   SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
3947   SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
3948   SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
3949
3950   artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3951   artwork.snd_current_identifier = artwork.snd_current->identifier;
3952   artwork.mus_current_identifier = artwork.mus_current->identifier;
3953
3954 #if ENABLE_UNUSED_CODE
3955   Debug("setup:LoadArtworkInfo", "graphics set == %s",
3956         artwork.gfx_current_identifier);
3957   Debug("setup:LoadArtworkInfo", "sounds set == %s",
3958         artwork.snd_current_identifier);
3959   Debug("setup:LoadArtworkInfo", "music set == %s",
3960         artwork.mus_current_identifier);
3961 #endif
3962
3963   sortTreeInfo(&artwork.gfx_first);
3964   sortTreeInfo(&artwork.snd_first);
3965   sortTreeInfo(&artwork.mus_first);
3966
3967 #if ENABLE_UNUSED_CODE
3968   dumpTreeInfo(artwork.gfx_first, 0);
3969   dumpTreeInfo(artwork.snd_first, 0);
3970   dumpTreeInfo(artwork.mus_first, 0);
3971 #endif
3972 }
3973
3974 static void MoveArtworkInfoIntoSubTree(ArtworkDirTree **artwork_node)
3975 {
3976   ArtworkDirTree *artwork_new = newTreeInfo();
3977   char *top_node_name = "standalone artwork";
3978
3979   setTreeInfoToDefaults(artwork_new, (*artwork_node)->type);
3980
3981   artwork_new->level_group = TRUE;
3982
3983   setString(&artwork_new->identifier,   top_node_name);
3984   setString(&artwork_new->name,         top_node_name);
3985   setString(&artwork_new->name_sorting, top_node_name);
3986
3987   // create node to link back to current custom artwork directory
3988   createParentTreeInfoNode(artwork_new);
3989
3990   // move existing custom artwork tree into newly created sub-tree
3991   artwork_new->node_group->next = *artwork_node;
3992
3993   // change custom artwork tree to contain only newly created node
3994   *artwork_node = artwork_new;
3995 }
3996
3997 static void LoadArtworkInfoFromLevelInfoExt(ArtworkDirTree **artwork_node,
3998                                             ArtworkDirTree *node_parent,
3999                                             LevelDirTree *level_node,
4000                                             boolean empty_level_set_mode)
4001 {
4002   int type = (*artwork_node)->type;
4003
4004   // recursively check all level directories for artwork sub-directories
4005
4006   while (level_node)
4007   {
4008     boolean empty_level_set = (level_node->levels == 0);
4009
4010     // check all tree entries for artwork, but skip parent link entries
4011     if (!level_node->parent_link && empty_level_set == empty_level_set_mode)
4012     {
4013       TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
4014       boolean cached = (artwork_new != NULL);
4015
4016       if (cached)
4017       {
4018         pushTreeInfo(artwork_node, artwork_new);
4019       }
4020       else
4021       {
4022         TreeInfo *topnode_last = *artwork_node;
4023         char *path = getPath2(getLevelDirFromTreeInfo(level_node),
4024                               ARTWORK_DIRECTORY(type));
4025
4026         LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
4027
4028         if (topnode_last != *artwork_node)      // check for newly added node
4029         {
4030           artwork_new = *artwork_node;
4031
4032           setString(&artwork_new->identifier,   level_node->subdir);
4033           setString(&artwork_new->name,         level_node->name);
4034           setString(&artwork_new->name_sorting, level_node->name_sorting);
4035
4036           artwork_new->sort_priority = level_node->sort_priority;
4037           artwork_new->color = LEVELCOLOR(artwork_new);
4038
4039           update_artworkinfo_cache = TRUE;
4040         }
4041
4042         free(path);
4043       }
4044
4045       // insert artwork info (from old cache or filesystem) into new cache
4046       if (artwork_new != NULL)
4047         setArtworkInfoCacheEntry(artwork_new, level_node, type);
4048     }
4049
4050     DrawInitText(level_node->name, 150, FC_YELLOW);
4051
4052     if (level_node->node_group != NULL)
4053     {
4054       TreeInfo *artwork_new = newTreeInfo();
4055
4056       if (node_parent)
4057         setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4058       else
4059         setTreeInfoToDefaults(artwork_new, type);
4060
4061       artwork_new->level_group = TRUE;
4062
4063       setString(&artwork_new->identifier,   level_node->subdir);
4064
4065       if (node_parent == NULL)          // check for top tree node
4066       {
4067         char *top_node_name = (empty_level_set_mode ?
4068                                "artwork for certain level sets" :
4069                                "artwork included in level sets");
4070
4071         setString(&artwork_new->name,         top_node_name);
4072         setString(&artwork_new->name_sorting, top_node_name);
4073       }
4074       else
4075       {
4076         setString(&artwork_new->name,         level_node->name);
4077         setString(&artwork_new->name_sorting, level_node->name_sorting);
4078       }
4079
4080       pushTreeInfo(artwork_node, artwork_new);
4081
4082       // create node to link back to current custom artwork directory
4083       createParentTreeInfoNode(artwork_new);
4084
4085       // recursively step into sub-directory and look for more custom artwork
4086       LoadArtworkInfoFromLevelInfoExt(&artwork_new->node_group, artwork_new,
4087                                       level_node->node_group,
4088                                       empty_level_set_mode);
4089
4090       // if sub-tree has no custom artwork at all, remove it
4091       if (artwork_new->node_group->next == NULL)
4092         removeTreeInfo(artwork_node);
4093     }
4094
4095     level_node = level_node->next;
4096   }
4097 }
4098
4099 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node)
4100 {
4101   // move peviously loaded artwork tree into separate sub-tree
4102   MoveArtworkInfoIntoSubTree(artwork_node);
4103
4104   // load artwork from level sets into separate sub-trees
4105   LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, TRUE);
4106   LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, FALSE);
4107
4108   // add top tree node over all three separate sub-trees
4109   *artwork_node = createTopTreeInfoNode(*artwork_node);
4110
4111   // set all parent links (back links) in complete artwork tree
4112   setTreeInfoParentNodes(*artwork_node, NULL);
4113 }
4114
4115 void LoadLevelArtworkInfo(void)
4116 {
4117   print_timestamp_init("LoadLevelArtworkInfo");
4118
4119   DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
4120
4121   print_timestamp_time("DrawTimeText");
4122
4123   LoadArtworkInfoFromLevelInfo(&artwork.gfx_first);
4124   print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4125   LoadArtworkInfoFromLevelInfo(&artwork.snd_first);
4126   print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4127   LoadArtworkInfoFromLevelInfo(&artwork.mus_first);
4128   print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4129
4130   SaveArtworkInfoCache();
4131
4132   print_timestamp_time("SaveArtworkInfoCache");
4133
4134   // needed for reloading level artwork not known at ealier stage
4135   ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
4136   ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
4137   ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
4138
4139   print_timestamp_time("getTreeInfoFromIdentifier");
4140
4141   sortTreeInfo(&artwork.gfx_first);
4142   sortTreeInfo(&artwork.snd_first);
4143   sortTreeInfo(&artwork.mus_first);
4144
4145   print_timestamp_time("sortTreeInfo");
4146
4147 #if ENABLE_UNUSED_CODE
4148   dumpTreeInfo(artwork.gfx_first, 0);
4149   dumpTreeInfo(artwork.snd_first, 0);
4150   dumpTreeInfo(artwork.mus_first, 0);
4151 #endif
4152
4153   print_timestamp_done("LoadLevelArtworkInfo");
4154 }
4155
4156 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
4157                                        char *tree_subdir_new, int type)
4158 {
4159   if (tree_node_old == NULL)
4160   {
4161     if (type == TREE_TYPE_LEVEL_DIR)
4162     {
4163       // get level info tree node of personal user level set
4164       tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
4165
4166       // this may happen if "setup.internal.create_user_levelset" is FALSE
4167       // or if file "levelinfo.conf" is missing in personal user level set
4168       if (tree_node_old == NULL)
4169         tree_node_old = leveldir_first->node_group;
4170     }
4171     else
4172     {
4173       // get artwork info tree node of first artwork set
4174       tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4175     }
4176   }
4177
4178   if (tree_dir == NULL)
4179     tree_dir = TREE_USERDIR(type);
4180
4181   if (tree_node_old   == NULL ||
4182       tree_dir        == NULL ||
4183       tree_subdir_new == NULL)          // should not happen
4184     return FALSE;
4185
4186   int draw_deactivation_mask = GetDrawDeactivationMask();
4187
4188   // override draw deactivation mask (temporarily disable drawing)
4189   SetDrawDeactivationMask(REDRAW_ALL);
4190
4191   if (type == TREE_TYPE_LEVEL_DIR)
4192   {
4193     // load new level set config and add it next to first user level set
4194     LoadLevelInfoFromLevelConf(&tree_node_old->next,
4195                                tree_node_old->node_parent,
4196                                tree_dir, tree_subdir_new);
4197   }
4198   else
4199   {
4200     // load new artwork set config and add it next to first artwork set
4201     LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4202                                    tree_node_old->node_parent,
4203                                    tree_dir, tree_subdir_new, type);
4204   }
4205
4206   // set draw deactivation mask to previous value
4207   SetDrawDeactivationMask(draw_deactivation_mask);
4208
4209   // get first node of level or artwork info tree
4210   TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4211
4212   // get tree info node of newly added level or artwork set
4213   TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4214                                                       tree_subdir_new);
4215
4216   if (tree_node_new == NULL)            // should not happen
4217     return FALSE;
4218
4219   // correct top link and parent node link of newly created tree node
4220   tree_node_new->node_top    = tree_node_old->node_top;
4221   tree_node_new->node_parent = tree_node_old->node_parent;
4222
4223   // sort tree info to adjust position of newly added tree set
4224   sortTreeInfo(tree_node_first);
4225
4226   return TRUE;
4227 }
4228
4229 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4230                           char *tree_subdir_new, int type)
4231 {
4232   if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4233     Fail("internal tree info structure corrupted -- aborting");
4234 }
4235
4236 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4237 {
4238   AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4239 }
4240
4241 char *getArtworkIdentifierForUserLevelSet(int type)
4242 {
4243   char *classic_artwork_set = getClassicArtworkSet(type);
4244
4245   // check for custom artwork configured in "levelinfo.conf"
4246   char *leveldir_artwork_set =
4247     *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4248   boolean has_leveldir_artwork_set =
4249     (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4250                                                classic_artwork_set));
4251
4252   // check for custom artwork in sub-directory "graphics" etc.
4253   TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4254   char *leveldir_identifier = leveldir_current->identifier;
4255   boolean has_artwork_subdir =
4256     (getTreeInfoFromIdentifier(artwork_first_node,
4257                                leveldir_identifier) != NULL);
4258
4259   return (has_leveldir_artwork_set ? leveldir_artwork_set :
4260           has_artwork_subdir       ? leveldir_identifier :
4261           classic_artwork_set);
4262 }
4263
4264 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4265 {
4266   char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4267   TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4268   TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4269
4270   if (ti == NULL)
4271   {
4272     ti = getTreeInfoFromIdentifier(artwork_first_node,
4273                                    ARTWORK_DEFAULT_SUBDIR(type));
4274     if (ti == NULL)
4275       Fail("cannot find default graphics -- should not happen");
4276   }
4277
4278   return ti;
4279 }
4280
4281 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4282 {
4283   char *graphics_set =
4284     getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4285   char *sounds_set =
4286     getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4287   char *music_set =
4288     getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4289
4290   return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4291           !strEqual(sounds_set,   SND_CLASSIC_SUBDIR) ||
4292           !strEqual(music_set,    MUS_CLASSIC_SUBDIR));
4293 }
4294
4295 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4296                            char *level_author, int num_levels)
4297 {
4298   char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4299   char *filename_tmp = getStringCat2(filename, ".tmp");
4300   FILE *file = NULL;
4301   FILE *file_tmp = NULL;
4302   char line[MAX_LINE_LEN];
4303   boolean success = FALSE;
4304   LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4305                                                      level_subdir);
4306   // update values in level directory tree
4307
4308   if (level_name != NULL)
4309     setString(&leveldir->name, level_name);
4310
4311   if (level_author != NULL)
4312     setString(&leveldir->author, level_author);
4313
4314   if (num_levels != -1)
4315     leveldir->levels = num_levels;
4316
4317   // update values that depend on other values
4318
4319   setString(&leveldir->name_sorting, leveldir->name);
4320
4321   leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4322
4323   // sort order of level sets may have changed
4324   sortTreeInfo(&leveldir_first);
4325
4326   if ((file     = fopen(filename,     MODE_READ)) &&
4327       (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4328   {
4329     while (fgets(line, MAX_LINE_LEN, file))
4330     {
4331       if (strPrefix(line, "name:") && level_name != NULL)
4332         fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4333       else if (strPrefix(line, "author:") && level_author != NULL)
4334         fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4335       else if (strPrefix(line, "levels:") && num_levels != -1)
4336         fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4337       else
4338         fputs(line, file_tmp);
4339     }
4340
4341     success = TRUE;
4342   }
4343
4344   if (file)
4345     fclose(file);
4346
4347   if (file_tmp)
4348     fclose(file_tmp);
4349
4350   if (success)
4351     success = (rename(filename_tmp, filename) == 0);
4352
4353   free(filename);
4354   free(filename_tmp);
4355
4356   return success;
4357 }
4358
4359 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4360                            char *level_author, int num_levels,
4361                            boolean use_artwork_set)
4362 {
4363   LevelDirTree *level_info;
4364   char *filename;
4365   FILE *file;
4366   int i;
4367
4368   // create user level sub-directory, if needed
4369   createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
4370
4371   filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4372
4373   if (!(file = fopen(filename, MODE_WRITE)))
4374   {
4375     Warn("cannot write level info file '%s'", filename);
4376
4377     free(filename);
4378
4379     return FALSE;
4380   }
4381
4382   level_info = newTreeInfo();
4383
4384   // always start with reliable default values
4385   setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4386
4387   setString(&level_info->name, level_name);
4388   setString(&level_info->author, level_author);
4389   level_info->levels = num_levels;
4390   level_info->first_level = 1;
4391   level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4392   level_info->readonly = FALSE;
4393
4394   if (use_artwork_set)
4395   {
4396     level_info->graphics_set =
4397       getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4398     level_info->sounds_set =
4399       getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4400     level_info->music_set =
4401       getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4402   }
4403
4404   token_value_position = TOKEN_VALUE_POSITION_SHORT;
4405
4406   fprintFileHeader(file, LEVELINFO_FILENAME);
4407
4408   ldi = *level_info;
4409   for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4410   {
4411     if (i == LEVELINFO_TOKEN_NAME ||
4412         i == LEVELINFO_TOKEN_AUTHOR ||
4413         i == LEVELINFO_TOKEN_LEVELS ||
4414         i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4415         i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4416         i == LEVELINFO_TOKEN_READONLY ||
4417         (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4418                              i == LEVELINFO_TOKEN_SOUNDS_SET ||
4419                              i == LEVELINFO_TOKEN_MUSIC_SET)))
4420       fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4421
4422     // just to make things nicer :)
4423     if (i == LEVELINFO_TOKEN_AUTHOR ||
4424         i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4425         (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4426       fprintf(file, "\n");      
4427   }
4428
4429   token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4430
4431   fclose(file);
4432
4433   SetFilePermissions(filename, PERMS_PRIVATE);
4434
4435   freeTreeInfo(level_info);
4436   free(filename);
4437
4438   return TRUE;
4439 }
4440
4441 static void SaveUserLevelInfo(void)
4442 {
4443   CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4444 }
4445
4446 char *getSetupValue(int type, void *value)
4447 {
4448   static char value_string[MAX_LINE_LEN];
4449
4450   if (value == NULL)
4451     return NULL;
4452
4453   switch (type)
4454   {
4455     case TYPE_BOOLEAN:
4456       strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4457       break;
4458
4459     case TYPE_SWITCH:
4460       strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4461       break;
4462
4463     case TYPE_SWITCH3:
4464       strcpy(value_string, (*(int *)value == AUTO  ? "auto" :
4465                             *(int *)value == FALSE ? "off" : "on"));
4466       break;
4467
4468     case TYPE_YES_NO:
4469       strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4470       break;
4471
4472     case TYPE_YES_NO_AUTO:
4473       strcpy(value_string, (*(int *)value == AUTO  ? "auto" :
4474                             *(int *)value == FALSE ? "no" : "yes"));
4475       break;
4476
4477     case TYPE_ECS_AGA:
4478       strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4479       break;
4480
4481     case TYPE_KEY:
4482       strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4483       break;
4484
4485     case TYPE_KEY_X11:
4486       strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4487       break;
4488
4489     case TYPE_INTEGER:
4490       sprintf(value_string, "%d", *(int *)value);
4491       break;
4492
4493     case TYPE_STRING:
4494       if (*(char **)value == NULL)
4495         return NULL;
4496
4497       strcpy(value_string, *(char **)value);
4498       break;
4499
4500     case TYPE_PLAYER:
4501       sprintf(value_string, "player_%d", *(int *)value + 1);
4502       break;
4503
4504     default:
4505       value_string[0] = '\0';
4506       break;
4507   }
4508
4509   if (type & TYPE_GHOSTED)
4510     strcpy(value_string, "n/a");
4511
4512   return value_string;
4513 }
4514
4515 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4516 {
4517   int i;
4518   char *line;
4519   static char token_string[MAX_LINE_LEN];
4520   int token_type = token_info[token_nr].type;
4521   void *setup_value = token_info[token_nr].value;
4522   char *token_text = token_info[token_nr].text;
4523   char *value_string = getSetupValue(token_type, setup_value);
4524
4525   // build complete token string
4526   sprintf(token_string, "%s%s", prefix, token_text);
4527
4528   // build setup entry line
4529   line = getFormattedSetupEntry(token_string, value_string);
4530
4531   if (token_type == TYPE_KEY_X11)
4532   {
4533     Key key = *(Key *)setup_value;
4534     char *keyname = getKeyNameFromKey(key);
4535
4536     // add comment, if useful
4537     if (!strEqual(keyname, "(undefined)") &&
4538         !strEqual(keyname, "(unknown)"))
4539     {
4540       // add at least one whitespace
4541       strcat(line, " ");
4542       for (i = strlen(line); i < token_comment_position; i++)
4543         strcat(line, " ");
4544
4545       strcat(line, "# ");
4546       strcat(line, keyname);
4547     }
4548   }
4549
4550   return line;
4551 }
4552
4553 static void InitLastPlayedLevels_ParentNode(void)
4554 {
4555   LevelDirTree **leveldir_top = &leveldir_first->node_group->next;
4556   LevelDirTree *leveldir_new = NULL;
4557
4558   // check if parent node for last played levels already exists
4559   if (strEqual((*leveldir_top)->identifier, TOKEN_STR_LAST_LEVEL_SERIES))
4560     return;
4561
4562   leveldir_new = newTreeInfo();
4563
4564   setTreeInfoToDefaultsFromParent(leveldir_new, leveldir_first);
4565
4566   leveldir_new->level_group = TRUE;
4567
4568   setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES);
4569   setString(&leveldir_new->name, "<< (last played level sets)");
4570
4571   pushTreeInfo(leveldir_top, leveldir_new);
4572
4573   // create node to link back to current level directory
4574   createParentTreeInfoNode(leveldir_new);
4575 }
4576
4577 void UpdateLastPlayedLevels_TreeInfo(void)
4578 {
4579   char **last_level_series = setup.level_setup.last_level_series;
4580   boolean reset_leveldir_current = FALSE;
4581   LevelDirTree *leveldir_last;
4582   TreeInfo **node_new = NULL;
4583   int i;
4584
4585   if (last_level_series[0] == NULL)
4586     return;
4587
4588   InitLastPlayedLevels_ParentNode();
4589
4590   // check if current level set is from "last played" sub-tree to be rebuilt
4591   reset_leveldir_current = strEqual(leveldir_current->node_parent->identifier,
4592                                     TOKEN_STR_LAST_LEVEL_SERIES);
4593
4594   leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first,
4595                                                TOKEN_STR_LAST_LEVEL_SERIES,
4596                                                TRUE);
4597   if (leveldir_last == NULL)
4598     return;
4599
4600   node_new = &leveldir_last->node_group->next;
4601
4602   freeTreeInfo(*node_new);
4603
4604   for (i = 0; last_level_series[i] != NULL; i++)
4605   {
4606     LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first,
4607                                                         last_level_series[i]);
4608
4609     *node_new = getTreeInfoCopy(node_last);     // copy complete node
4610
4611     (*node_new)->node_top = &leveldir_first;    // correct top node link
4612     (*node_new)->node_parent = leveldir_last;   // correct parent node link
4613
4614     (*node_new)->node_group = NULL;
4615     (*node_new)->next = NULL;
4616
4617     (*node_new)->cl_first = -1;                 // force setting tree cursor
4618
4619     node_new = &((*node_new)->next);
4620   }
4621
4622   if (reset_leveldir_current)
4623     leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4624                                                  last_level_series[0]);
4625 }
4626
4627 static void UpdateLastPlayedLevels_List(void)
4628 {
4629   char **last_level_series = setup.level_setup.last_level_series;
4630   int pos = MAX_LEVELDIR_HISTORY - 1;
4631   int i;
4632
4633   // search for potentially already existing entry in list of level sets
4634   for (i = 0; last_level_series[i] != NULL; i++)
4635     if (strEqual(last_level_series[i], leveldir_current->identifier))
4636       pos = i;
4637
4638   // move list of level sets one entry down (using potentially free entry)
4639   for (i = pos; i > 0; i--)
4640     setString(&last_level_series[i], last_level_series[i - 1]);
4641
4642   // put last played level set at top position
4643   setString(&last_level_series[0], leveldir_current->identifier);
4644 }
4645
4646 void LoadLevelSetup_LastSeries(void)
4647 {
4648   // --------------------------------------------------------------------------
4649   // ~/.<program>/levelsetup.conf
4650   // --------------------------------------------------------------------------
4651
4652   char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4653   SetupFileHash *level_setup_hash = NULL;
4654   int pos = 0;
4655   int i;
4656
4657   // always start with reliable default values
4658   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4659
4660   // start with empty history of last played level sets
4661   setString(&setup.level_setup.last_level_series[0], NULL);
4662
4663   if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4664   {
4665     leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4666                                                  DEFAULT_LEVELSET);
4667     if (leveldir_current == NULL)
4668       leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4669   }
4670
4671   if ((level_setup_hash = loadSetupFileHash(filename)))
4672   {
4673     char *last_level_series =
4674       getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4675
4676     leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4677                                                  last_level_series);
4678     if (leveldir_current == NULL)
4679       leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4680
4681     for (i = 0; i < MAX_LEVELDIR_HISTORY; i++)
4682     {
4683       char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4684       LevelDirTree *leveldir_last;
4685
4686       sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4687
4688       last_level_series = getHashEntry(level_setup_hash, token);
4689
4690       leveldir_last = getTreeInfoFromIdentifier(leveldir_first,
4691                                                 last_level_series);
4692       if (leveldir_last != NULL)
4693         setString(&setup.level_setup.last_level_series[pos++],
4694                   last_level_series);
4695     }
4696
4697     setString(&setup.level_setup.last_level_series[pos], NULL);
4698
4699     freeSetupFileHash(level_setup_hash);
4700   }
4701   else
4702   {
4703     Debug("setup", "using default setup values");
4704   }
4705
4706   free(filename);
4707 }
4708
4709 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4710 {
4711   // --------------------------------------------------------------------------
4712   // ~/.<program>/levelsetup.conf
4713   // --------------------------------------------------------------------------
4714
4715   // check if the current level directory structure is available at this point
4716   if (leveldir_current == NULL)
4717     return;
4718
4719   char **last_level_series = setup.level_setup.last_level_series;
4720   char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4721   FILE *file;
4722   int i;
4723
4724   InitUserDataDirectory();
4725
4726   UpdateLastPlayedLevels_List();
4727
4728   if (!(file = fopen(filename, MODE_WRITE)))
4729   {
4730     Warn("cannot write setup file '%s'", filename);
4731
4732     free(filename);
4733
4734     return;
4735   }
4736
4737   fprintFileHeader(file, LEVELSETUP_FILENAME);
4738
4739   if (deactivate_last_level_series)
4740     fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4741
4742   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4743                                                leveldir_current->identifier));
4744
4745   for (i = 0; last_level_series[i] != NULL; i++)
4746   {
4747     char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4748
4749     sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4750
4751     fprintf(file, "%s\n", getFormattedSetupEntry(token, last_level_series[i]));
4752   }
4753
4754   fclose(file);
4755
4756   SetFilePermissions(filename, PERMS_PRIVATE);
4757
4758   free(filename);
4759 }
4760
4761 void SaveLevelSetup_LastSeries(void)
4762 {
4763   SaveLevelSetup_LastSeries_Ext(FALSE);
4764 }
4765
4766 void SaveLevelSetup_LastSeries_Deactivate(void)
4767 {
4768   SaveLevelSetup_LastSeries_Ext(TRUE);
4769 }
4770
4771 static void checkSeriesInfo(void)
4772 {
4773   static char *level_directory = NULL;
4774   Directory *dir;
4775 #if 0
4776   DirectoryEntry *dir_entry;
4777 #endif
4778
4779   checked_free(level_directory);
4780
4781   // check for more levels besides the 'levels' field of 'levelinfo.conf'
4782
4783   level_directory = getPath2((leveldir_current->in_user_dir ?
4784                               getUserLevelDir(NULL) :
4785                               options.level_directory),
4786                              leveldir_current->fullpath);
4787
4788   if ((dir = openDirectory(level_directory)) == NULL)
4789   {
4790     Warn("cannot read level directory '%s'", level_directory);
4791
4792     return;
4793   }
4794
4795 #if 0
4796   while ((dir_entry = readDirectory(dir)) != NULL)      // loop all entries
4797   {
4798     if (strlen(dir_entry->basename) > 4 &&
4799         dir_entry->basename[3] == '.' &&
4800         strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
4801     {
4802       char levelnum_str[4];
4803       int levelnum_value;
4804
4805       strncpy(levelnum_str, dir_entry->basename, 3);
4806       levelnum_str[3] = '\0';
4807
4808       levelnum_value = atoi(levelnum_str);
4809
4810       if (levelnum_value < leveldir_current->first_level)
4811       {
4812         Warn("additional level %d found", levelnum_value);
4813
4814         leveldir_current->first_level = levelnum_value;
4815       }
4816       else if (levelnum_value > leveldir_current->last_level)
4817       {
4818         Warn("additional level %d found", levelnum_value);
4819
4820         leveldir_current->last_level = levelnum_value;
4821       }
4822     }
4823   }
4824 #endif
4825
4826   closeDirectory(dir);
4827 }
4828
4829 void LoadLevelSetup_SeriesInfo(void)
4830 {
4831   char *filename;
4832   SetupFileHash *level_setup_hash = NULL;
4833   char *level_subdir = leveldir_current->subdir;
4834   int i;
4835
4836   // always start with reliable default values
4837   level_nr = leveldir_current->first_level;
4838
4839   for (i = 0; i < MAX_LEVELS; i++)
4840   {
4841     LevelStats_setPlayed(i, 0);
4842     LevelStats_setSolved(i, 0);
4843   }
4844
4845   checkSeriesInfo();
4846
4847   // --------------------------------------------------------------------------
4848   // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4849   // --------------------------------------------------------------------------
4850
4851   level_subdir = leveldir_current->subdir;
4852
4853   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4854
4855   if ((level_setup_hash = loadSetupFileHash(filename)))
4856   {
4857     char *token_value;
4858
4859     // get last played level in this level set
4860
4861     token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4862
4863     if (token_value)
4864     {
4865       level_nr = atoi(token_value);
4866
4867       if (level_nr < leveldir_current->first_level)
4868         level_nr = leveldir_current->first_level;
4869       if (level_nr > leveldir_current->last_level)
4870         level_nr = leveldir_current->last_level;
4871     }
4872
4873     // get handicap level in this level set
4874
4875     token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4876
4877     if (token_value)
4878     {
4879       int level_nr = atoi(token_value);
4880
4881       if (level_nr < leveldir_current->first_level)
4882         level_nr = leveldir_current->first_level;
4883       if (level_nr > leveldir_current->last_level + 1)
4884         level_nr = leveldir_current->last_level;
4885
4886       if (leveldir_current->user_defined || !leveldir_current->handicap)
4887         level_nr = leveldir_current->last_level;
4888
4889       leveldir_current->handicap_level = level_nr;
4890     }
4891
4892     // get number of played and solved levels in this level set
4893
4894     BEGIN_HASH_ITERATION(level_setup_hash, itr)
4895     {
4896       char *token = HASH_ITERATION_TOKEN(itr);
4897       char *value = HASH_ITERATION_VALUE(itr);
4898
4899       if (strlen(token) == 3 &&
4900           token[0] >= '0' && token[0] <= '9' &&
4901           token[1] >= '0' && token[1] <= '9' &&
4902           token[2] >= '0' && token[2] <= '9')
4903       {
4904         int level_nr = atoi(token);
4905
4906         if (value != NULL)
4907           LevelStats_setPlayed(level_nr, atoi(value));  // read 1st column
4908
4909         value = strchr(value, ' ');
4910
4911         if (value != NULL)
4912           LevelStats_setSolved(level_nr, atoi(value));  // read 2nd column
4913       }
4914     }
4915     END_HASH_ITERATION(hash, itr)
4916
4917     freeSetupFileHash(level_setup_hash);
4918   }
4919   else
4920   {
4921     Debug("setup", "using default setup values");
4922   }
4923
4924   free(filename);
4925 }
4926
4927 void SaveLevelSetup_SeriesInfo(void)
4928 {
4929   char *filename;
4930   char *level_subdir = leveldir_current->subdir;
4931   char *level_nr_str = int2str(level_nr, 0);
4932   char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4933   FILE *file;
4934   int i;
4935
4936   // --------------------------------------------------------------------------
4937   // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4938   // --------------------------------------------------------------------------
4939
4940   InitLevelSetupDirectory(level_subdir);
4941
4942   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4943
4944   if (!(file = fopen(filename, MODE_WRITE)))
4945   {
4946     Warn("cannot write setup file '%s'", filename);
4947
4948     free(filename);
4949
4950     return;
4951   }
4952
4953   fprintFileHeader(file, LEVELSETUP_FILENAME);
4954
4955   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4956                                                level_nr_str));
4957   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4958                                                  handicap_level_str));
4959
4960   for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
4961        i++)
4962   {
4963     if (LevelStats_getPlayed(i) > 0 ||
4964         LevelStats_getSolved(i) > 0)
4965     {
4966       char token[16];
4967       char value[16];
4968
4969       sprintf(token, "%03d", i);
4970       sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
4971
4972       fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
4973     }
4974   }
4975
4976   fclose(file);
4977
4978   SetFilePermissions(filename, PERMS_PRIVATE);
4979
4980   free(filename);
4981 }
4982
4983 int LevelStats_getPlayed(int nr)
4984 {
4985   return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
4986 }
4987
4988 int LevelStats_getSolved(int nr)
4989 {
4990   return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
4991 }
4992
4993 void LevelStats_setPlayed(int nr, int value)
4994 {
4995   if (nr >= 0 && nr < MAX_LEVELS)
4996     level_stats[nr].played = value;
4997 }
4998
4999 void LevelStats_setSolved(int nr, int value)
5000 {
5001   if (nr >= 0 && nr < MAX_LEVELS)
5002     level_stats[nr].solved = value;
5003 }
5004
5005 void LevelStats_incPlayed(int nr)
5006 {
5007   if (nr >= 0 && nr < MAX_LEVELS)
5008     level_stats[nr].played++;
5009 }
5010
5011 void LevelStats_incSolved(int nr)
5012 {
5013   if (nr >= 0 && nr < MAX_LEVELS)
5014     level_stats[nr].solved++;
5015 }
5016
5017 void LoadUserSetup(void)
5018 {
5019   // --------------------------------------------------------------------------
5020   // ~/.<program>/usersetup.conf
5021   // --------------------------------------------------------------------------
5022
5023   char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5024   SetupFileHash *user_setup_hash = NULL;
5025
5026   // always start with reliable default values
5027   user.nr = 0;
5028
5029   if ((user_setup_hash = loadSetupFileHash(filename)))
5030   {
5031     char *token_value;
5032
5033     // get last selected user number
5034     token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
5035
5036     if (token_value)
5037       user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
5038
5039     freeSetupFileHash(user_setup_hash);
5040   }
5041   else
5042   {
5043     Debug("setup", "using default setup values");
5044   }
5045
5046   free(filename);
5047 }
5048
5049 void SaveUserSetup(void)
5050 {
5051   // --------------------------------------------------------------------------
5052   // ~/.<program>/usersetup.conf
5053   // --------------------------------------------------------------------------
5054
5055   char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5056   FILE *file;
5057
5058   InitMainUserDataDirectory();
5059
5060   if (!(file = fopen(filename, MODE_WRITE)))
5061   {
5062     Warn("cannot write setup file '%s'", filename);
5063
5064     free(filename);
5065
5066     return;
5067   }
5068
5069   fprintFileHeader(file, USERSETUP_FILENAME);
5070
5071   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
5072                                                i_to_a(user.nr)));
5073   fclose(file);
5074
5075   SetFilePermissions(filename, PERMS_PRIVATE);
5076
5077   free(filename);
5078 }