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