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