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