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