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