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