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