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