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