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