1 /***********************************************************
2 * Artsoft Retro-Game Library *
3 *----------------------------------------------------------*
4 * (c) 1994-2006 Artsoft Entertainment *
6 * Detmolder Strasse 189 *
9 * e-mail: info@artsoft.org *
10 *----------------------------------------------------------*
12 ***********************************************************/
14 #include <sys/types.h>
22 #if !defined(PLATFORM_WIN32)
24 #include <sys/param.h>
34 #define NUM_LEVELCLASS_DESC 8
36 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
49 #define LEVELCOLOR(n) (IS_LEVELCLASS_TUTORIAL(n) ? FC_BLUE : \
50 IS_LEVELCLASS_CLASSICS(n) ? FC_RED : \
51 IS_LEVELCLASS_BD(n) ? FC_YELLOW : \
52 IS_LEVELCLASS_EM(n) ? FC_YELLOW : \
53 IS_LEVELCLASS_SP(n) ? FC_YELLOW : \
54 IS_LEVELCLASS_DX(n) ? FC_YELLOW : \
55 IS_LEVELCLASS_SB(n) ? FC_YELLOW : \
56 IS_LEVELCLASS_CONTRIB(n) ? FC_GREEN : \
57 IS_LEVELCLASS_PRIVATE(n) ? FC_RED : \
60 #define LEVELSORTING(n) (IS_LEVELCLASS_TUTORIAL(n) ? 0 : \
61 IS_LEVELCLASS_CLASSICS(n) ? 1 : \
62 IS_LEVELCLASS_BD(n) ? 2 : \
63 IS_LEVELCLASS_EM(n) ? 3 : \
64 IS_LEVELCLASS_SP(n) ? 4 : \
65 IS_LEVELCLASS_DX(n) ? 5 : \
66 IS_LEVELCLASS_SB(n) ? 6 : \
67 IS_LEVELCLASS_CONTRIB(n) ? 7 : \
68 IS_LEVELCLASS_PRIVATE(n) ? 8 : \
71 #define ARTWORKCOLOR(n) (IS_ARTWORKCLASS_CLASSICS(n) ? FC_RED : \
72 IS_ARTWORKCLASS_CONTRIB(n) ? FC_GREEN : \
73 IS_ARTWORKCLASS_PRIVATE(n) ? FC_RED : \
74 IS_ARTWORKCLASS_LEVEL(n) ? FC_YELLOW : \
77 #define ARTWORKSORTING(n) (IS_ARTWORKCLASS_CLASSICS(n) ? 0 : \
78 IS_ARTWORKCLASS_LEVEL(n) ? 1 : \
79 IS_ARTWORKCLASS_CONTRIB(n) ? 2 : \
80 IS_ARTWORKCLASS_PRIVATE(n) ? 3 : \
83 #define TOKEN_VALUE_POSITION_SHORT 32
84 #define TOKEN_VALUE_POSITION_DEFAULT 40
85 #define TOKEN_COMMENT_POSITION_DEFAULT 60
87 #define MAX_COOKIE_LEN 256
89 static void setTreeInfoToDefaults(TreeInfo *, int);
90 static int compareTreeInfoEntries(const void *, const void *);
92 static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
93 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
95 static SetupFileHash *artworkinfo_hash_old = NULL;
96 static SetupFileHash *artworkinfo_hash_new = NULL;
99 /* ------------------------------------------------------------------------- */
101 /* ------------------------------------------------------------------------- */
103 static char *getLevelClassDescription(TreeInfo *ti)
105 int position = ti->sort_priority / 100;
107 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
108 return levelclass_desc[position];
110 return "Unknown Level Class";
113 static char *getUserLevelDir(char *level_subdir)
115 static char *userlevel_dir = NULL;
116 char *data_dir = getUserGameDataDir();
117 char *userlevel_subdir = LEVELS_DIRECTORY;
119 checked_free(userlevel_dir);
121 if (level_subdir != NULL)
122 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
124 userlevel_dir = getPath2(data_dir, userlevel_subdir);
126 return userlevel_dir;
129 static char *getScoreDir(char *level_subdir)
131 static char *score_dir = NULL;
132 char *data_dir = getCommonDataDir();
133 char *score_subdir = SCORES_DIRECTORY;
135 checked_free(score_dir);
137 if (level_subdir != NULL)
138 score_dir = getPath3(data_dir, score_subdir, level_subdir);
140 score_dir = getPath2(data_dir, score_subdir);
145 static char *getLevelSetupDir(char *level_subdir)
147 static char *levelsetup_dir = NULL;
148 char *data_dir = getUserGameDataDir();
149 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
151 checked_free(levelsetup_dir);
153 if (level_subdir != NULL)
154 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
156 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
158 return levelsetup_dir;
161 static char *getLevelDirFromTreeInfo(TreeInfo *node)
163 static char *level_dir = NULL;
166 return options.level_directory;
168 checked_free(level_dir);
170 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
171 options.level_directory), node->fullpath);
176 char *getCurrentLevelDir()
178 return getLevelDirFromTreeInfo(leveldir_current);
181 static char *getTapeDir(char *level_subdir)
183 static char *tape_dir = NULL;
184 char *data_dir = getUserGameDataDir();
185 char *tape_subdir = TAPES_DIRECTORY;
187 checked_free(tape_dir);
189 if (level_subdir != NULL)
190 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
192 tape_dir = getPath2(data_dir, tape_subdir);
197 static char *getSolutionTapeDir()
199 static char *tape_dir = NULL;
200 char *data_dir = getCurrentLevelDir();
201 char *tape_subdir = TAPES_DIRECTORY;
203 checked_free(tape_dir);
205 tape_dir = getPath2(data_dir, tape_subdir);
210 static char *getDefaultGraphicsDir(char *graphics_subdir)
212 static char *graphics_dir = NULL;
214 if (graphics_subdir == NULL)
215 return options.graphics_directory;
217 checked_free(graphics_dir);
219 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
224 static char *getDefaultSoundsDir(char *sounds_subdir)
226 static char *sounds_dir = NULL;
228 if (sounds_subdir == NULL)
229 return options.sounds_directory;
231 checked_free(sounds_dir);
233 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
238 static char *getDefaultMusicDir(char *music_subdir)
240 static char *music_dir = NULL;
242 if (music_subdir == NULL)
243 return options.music_directory;
245 checked_free(music_dir);
247 music_dir = getPath2(options.music_directory, music_subdir);
252 static char *getDefaultArtworkSet(int type)
254 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
255 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
256 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
259 static char *getDefaultArtworkDir(int type)
261 return (type == TREE_TYPE_GRAPHICS_DIR ?
262 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
263 type == TREE_TYPE_SOUNDS_DIR ?
264 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
265 type == TREE_TYPE_MUSIC_DIR ?
266 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
269 static char *getUserGraphicsDir()
271 static char *usergraphics_dir = NULL;
273 if (usergraphics_dir == NULL)
274 usergraphics_dir = getPath2(getUserGameDataDir(), GRAPHICS_DIRECTORY);
276 return usergraphics_dir;
279 static char *getUserSoundsDir()
281 static char *usersounds_dir = NULL;
283 if (usersounds_dir == NULL)
284 usersounds_dir = getPath2(getUserGameDataDir(), SOUNDS_DIRECTORY);
286 return usersounds_dir;
289 static char *getUserMusicDir()
291 static char *usermusic_dir = NULL;
293 if (usermusic_dir == NULL)
294 usermusic_dir = getPath2(getUserGameDataDir(), MUSIC_DIRECTORY);
296 return usermusic_dir;
299 static char *getSetupArtworkDir(TreeInfo *ti)
301 static char *artwork_dir = NULL;
303 checked_free(artwork_dir);
305 artwork_dir = getPath2(ti->basepath, ti->fullpath);
310 char *setLevelArtworkDir(TreeInfo *ti)
312 char **artwork_path_ptr, **artwork_set_ptr;
313 TreeInfo *level_artwork;
315 if (ti == NULL || leveldir_current == NULL)
318 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
319 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
321 checked_free(*artwork_path_ptr);
323 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
324 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
327 /* No (or non-existing) artwork configured in "levelinfo.conf". This would
328 normally result in using the artwork configured in the setup menu. But
329 if an artwork subdirectory exists (which might contain custom artwork
330 or an artwork configuration file), this level artwork must be treated
331 as relative to the default "classic" artwork, not to the artwork that
332 is currently configured in the setup menu. */
334 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
336 checked_free(*artwork_set_ptr);
340 *artwork_path_ptr = getStringCopy(getDefaultArtworkDir(ti->type));
341 *artwork_set_ptr = getStringCopy(getDefaultArtworkSet(ti->type));
345 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
346 *artwork_set_ptr = NULL;
352 return *artwork_set_ptr;
355 inline static char *getLevelArtworkSet(int type)
357 if (leveldir_current == NULL)
360 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
363 inline static char *getLevelArtworkDir(int type)
365 if (leveldir_current == NULL)
366 return UNDEFINED_FILENAME;
368 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
371 char *getTapeFilename(int nr)
373 static char *filename = NULL;
374 char basename[MAX_FILENAME_LEN];
376 checked_free(filename);
378 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
379 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
384 char *getSolutionTapeFilename(int nr)
386 static char *filename = NULL;
387 char basename[MAX_FILENAME_LEN];
389 checked_free(filename);
391 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
392 filename = getPath2(getSolutionTapeDir(), basename);
397 char *getScoreFilename(int nr)
399 static char *filename = NULL;
400 char basename[MAX_FILENAME_LEN];
402 checked_free(filename);
404 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
405 filename = getPath2(getScoreDir(leveldir_current->subdir), basename);
410 char *getSetupFilename()
412 static char *filename = NULL;
414 checked_free(filename);
416 filename = getPath2(getSetupDir(), SETUP_FILENAME);
421 char *getEditorSetupFilename()
423 static char *filename = NULL;
425 checked_free(filename);
426 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
428 if (fileExists(filename))
431 checked_free(filename);
432 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
437 char *getHelpAnimFilename()
439 static char *filename = NULL;
441 checked_free(filename);
443 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
448 char *getHelpTextFilename()
450 static char *filename = NULL;
452 checked_free(filename);
454 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
459 char *getLevelSetInfoFilename()
461 static char *filename = NULL;
476 for (i = 0; basenames[i] != NULL; i++)
478 checked_free(filename);
479 filename = getPath2(getCurrentLevelDir(), basenames[i]);
481 if (fileExists(filename))
488 static char *getCorrectedArtworkBasename(char *basename)
490 char *basename_corrected = basename;
492 #if defined(PLATFORM_MSDOS)
493 if (program.filename_prefix != NULL)
495 int prefix_len = strlen(program.filename_prefix);
497 if (strncmp(basename, program.filename_prefix, prefix_len) == 0)
498 basename_corrected = &basename[prefix_len];
500 /* if corrected filename is still longer than standard MS-DOS filename
501 size (8 characters + 1 dot + 3 characters file extension), shorten
502 filename by writing file extension after 8th basename character */
503 if (strlen(basename_corrected) > 8 + 1 + 3)
505 static char *msdos_filename = NULL;
507 checked_free(msdos_filename);
509 msdos_filename = getStringCopy(basename_corrected);
510 strncpy(&msdos_filename[8], &basename[strlen(basename) - (1+3)], 1+3 +1);
512 basename_corrected = msdos_filename;
517 return basename_corrected;
520 char *getCustomImageFilename(char *basename)
522 static char *filename = NULL;
523 boolean skip_setup_artwork = FALSE;
525 checked_free(filename);
527 basename = getCorrectedArtworkBasename(basename);
529 if (!setup.override_level_graphics)
531 /* 1st try: look for special artwork in current level series directory */
532 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
533 if (fileExists(filename))
538 /* check if there is special artwork configured in level series config */
539 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
541 /* 2nd try: look for special artwork configured in level series config */
542 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
543 if (fileExists(filename))
548 /* take missing artwork configured in level set config from default */
549 skip_setup_artwork = TRUE;
553 if (!skip_setup_artwork)
555 /* 3rd try: look for special artwork in configured artwork directory */
556 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
557 if (fileExists(filename))
563 /* 4th try: look for default artwork in new default artwork directory */
564 filename = getPath2(getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR), basename);
565 if (fileExists(filename))
570 /* 5th try: look for default artwork in old default artwork directory */
571 filename = getPath2(options.graphics_directory, basename);
572 if (fileExists(filename))
575 return NULL; /* cannot find specified artwork file anywhere */
578 char *getCustomSoundFilename(char *basename)
580 static char *filename = NULL;
581 boolean skip_setup_artwork = FALSE;
583 checked_free(filename);
585 basename = getCorrectedArtworkBasename(basename);
587 if (!setup.override_level_sounds)
589 /* 1st try: look for special artwork in current level series directory */
590 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
591 if (fileExists(filename))
596 /* check if there is special artwork configured in level series config */
597 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
599 /* 2nd try: look for special artwork configured in level series config */
600 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
601 if (fileExists(filename))
606 /* take missing artwork configured in level set config from default */
607 skip_setup_artwork = TRUE;
611 if (!skip_setup_artwork)
613 /* 3rd try: look for special artwork in configured artwork directory */
614 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
615 if (fileExists(filename))
621 /* 4th try: look for default artwork in new default artwork directory */
622 filename = getPath2(getDefaultSoundsDir(SND_CLASSIC_SUBDIR), basename);
623 if (fileExists(filename))
628 /* 5th try: look for default artwork in old default artwork directory */
629 filename = getPath2(options.sounds_directory, basename);
630 if (fileExists(filename))
633 return NULL; /* cannot find specified artwork file anywhere */
636 char *getCustomMusicFilename(char *basename)
638 static char *filename = NULL;
639 boolean skip_setup_artwork = FALSE;
641 checked_free(filename);
643 basename = getCorrectedArtworkBasename(basename);
645 if (!setup.override_level_music)
647 /* 1st try: look for special artwork in current level series directory */
648 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
649 if (fileExists(filename))
654 /* check if there is special artwork configured in level series config */
655 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
657 /* 2nd try: look for special artwork configured in level series config */
658 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
659 if (fileExists(filename))
664 /* take missing artwork configured in level set config from default */
665 skip_setup_artwork = TRUE;
669 if (!skip_setup_artwork)
671 /* 3rd try: look for special artwork in configured artwork directory */
672 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
673 if (fileExists(filename))
679 /* 4th try: look for default artwork in new default artwork directory */
680 filename = getPath2(getDefaultMusicDir(MUS_CLASSIC_SUBDIR), basename);
681 if (fileExists(filename))
686 /* 5th try: look for default artwork in old default artwork directory */
687 filename = getPath2(options.music_directory, basename);
688 if (fileExists(filename))
691 return NULL; /* cannot find specified artwork file anywhere */
694 char *getCustomArtworkFilename(char *basename, int type)
696 if (type == ARTWORK_TYPE_GRAPHICS)
697 return getCustomImageFilename(basename);
698 else if (type == ARTWORK_TYPE_SOUNDS)
699 return getCustomSoundFilename(basename);
700 else if (type == ARTWORK_TYPE_MUSIC)
701 return getCustomMusicFilename(basename);
703 return UNDEFINED_FILENAME;
706 char *getCustomArtworkConfigFilename(int type)
708 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
711 char *getCustomArtworkLevelConfigFilename(int type)
713 static char *filename = NULL;
715 checked_free(filename);
717 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
722 char *getCustomMusicDirectory(void)
724 static char *directory = NULL;
725 boolean skip_setup_artwork = FALSE;
727 checked_free(directory);
729 if (!setup.override_level_music)
731 /* 1st try: look for special artwork in current level series directory */
732 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
733 if (fileExists(directory))
738 /* check if there is special artwork configured in level series config */
739 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
741 /* 2nd try: look for special artwork configured in level series config */
742 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
743 if (fileExists(directory))
748 /* take missing artwork configured in level set config from default */
749 skip_setup_artwork = TRUE;
753 if (!skip_setup_artwork)
755 /* 3rd try: look for special artwork in configured artwork directory */
756 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
757 if (fileExists(directory))
763 /* 4th try: look for default artwork in new default artwork directory */
764 directory = getStringCopy(getDefaultMusicDir(MUS_CLASSIC_SUBDIR));
765 if (fileExists(directory))
770 /* 5th try: look for default artwork in old default artwork directory */
771 directory = getStringCopy(options.music_directory);
772 if (fileExists(directory))
775 return NULL; /* cannot find specified artwork file anywhere */
778 void InitTapeDirectory(char *level_subdir)
780 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
781 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
782 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
785 void InitScoreDirectory(char *level_subdir)
787 createDirectory(getCommonDataDir(), "common data", PERMS_PUBLIC);
788 createDirectory(getScoreDir(NULL), "main score", PERMS_PUBLIC);
789 createDirectory(getScoreDir(level_subdir), "level score", PERMS_PUBLIC);
792 static void SaveUserLevelInfo();
794 void InitUserLevelDirectory(char *level_subdir)
796 if (!fileExists(getUserLevelDir(level_subdir)))
798 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
799 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
800 createDirectory(getUserLevelDir(level_subdir), "user level",PERMS_PRIVATE);
806 void InitLevelSetupDirectory(char *level_subdir)
808 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
809 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
810 createDirectory(getLevelSetupDir(level_subdir), "level setup",PERMS_PRIVATE);
814 /* ------------------------------------------------------------------------- */
815 /* some functions to handle lists of level and artwork directories */
816 /* ------------------------------------------------------------------------- */
818 TreeInfo *newTreeInfo()
820 return checked_calloc(sizeof(TreeInfo));
823 TreeInfo *newTreeInfo_setDefaults(int type)
825 TreeInfo *ti = newTreeInfo();
827 setTreeInfoToDefaults(ti, type);
832 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
834 node_new->next = *node_first;
835 *node_first = node_new;
838 int numTreeInfo(TreeInfo *node)
851 boolean validLevelSeries(TreeInfo *node)
853 return (node != NULL && !node->node_group && !node->parent_link);
856 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
861 if (node->node_group) /* enter level group (step down into tree) */
862 return getFirstValidTreeInfoEntry(node->node_group);
863 else if (node->parent_link) /* skip start entry of level group */
865 if (node->next) /* get first real level series entry */
866 return getFirstValidTreeInfoEntry(node->next);
867 else /* leave empty level group and go on */
868 return getFirstValidTreeInfoEntry(node->node_parent->next);
870 else /* this seems to be a regular level series */
874 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
879 if (node->node_parent == NULL) /* top level group */
880 return *node->node_top;
881 else /* sub level group */
882 return node->node_parent->node_group;
885 int numTreeInfoInGroup(TreeInfo *node)
887 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
890 int posTreeInfo(TreeInfo *node)
892 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
897 if (node_cmp == node)
901 node_cmp = node_cmp->next;
907 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
909 TreeInfo *node_default = node;
924 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
926 if (identifier == NULL)
931 if (node->node_group)
933 TreeInfo *node_group;
935 node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
940 else if (!node->parent_link)
942 if (strEqual(identifier, node->identifier))
952 TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
953 TreeInfo *node, boolean skip_sets_without_levels)
960 if (!node->parent_link && !node->level_group &&
961 skip_sets_without_levels && node->levels == 0)
962 return cloneTreeNode(node_top, node_parent, node->next,
963 skip_sets_without_levels);
965 node_new = newTreeInfo();
967 *node_new = *node; /* copy complete node */
969 node_new->node_top = node_top; /* correct top node link */
970 node_new->node_parent = node_parent; /* correct parent node link */
972 if (node->level_group)
973 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
974 skip_sets_without_levels);
976 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
977 skip_sets_without_levels);
982 void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
984 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
989 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
991 boolean settings_changed = FALSE;
995 if (node->graphics_set_ecs && !setup.prefer_aga_graphics &&
996 !strEqual(node->graphics_set, node->graphics_set_ecs))
998 setString(&node->graphics_set, node->graphics_set_ecs);
999 settings_changed = TRUE;
1001 else if (node->graphics_set_aga && setup.prefer_aga_graphics &&
1002 !strEqual(node->graphics_set, node->graphics_set_aga))
1004 setString(&node->graphics_set, node->graphics_set_aga);
1005 settings_changed = TRUE;
1008 if (node->node_group != NULL)
1009 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1014 return settings_changed;
1017 void dumpTreeInfo(TreeInfo *node, int depth)
1021 printf("Dumping TreeInfo:\n");
1025 for (i = 0; i < (depth + 1) * 3; i++)
1028 printf("subdir == '%s' ['%s', '%s'] [%d])\n",
1029 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1031 if (node->node_group != NULL)
1032 dumpTreeInfo(node->node_group, depth + 1);
1038 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1039 int (*compare_function)(const void *,
1042 int num_nodes = numTreeInfo(*node_first);
1043 TreeInfo **sort_array;
1044 TreeInfo *node = *node_first;
1050 /* allocate array for sorting structure pointers */
1051 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1053 /* writing structure pointers to sorting array */
1054 while (i < num_nodes && node) /* double boundary check... */
1056 sort_array[i] = node;
1062 /* sorting the structure pointers in the sorting array */
1063 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1066 /* update the linkage of list elements with the sorted node array */
1067 for (i = 0; i < num_nodes - 1; i++)
1068 sort_array[i]->next = sort_array[i + 1];
1069 sort_array[num_nodes - 1]->next = NULL;
1071 /* update the linkage of the main list anchor pointer */
1072 *node_first = sort_array[0];
1076 /* now recursively sort the level group structures */
1080 if (node->node_group != NULL)
1081 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1087 void sortTreeInfo(TreeInfo **node_first)
1089 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1093 /* ========================================================================= */
1094 /* some stuff from "files.c" */
1095 /* ========================================================================= */
1097 #if defined(PLATFORM_WIN32)
1099 #define S_IRGRP S_IRUSR
1102 #define S_IROTH S_IRUSR
1105 #define S_IWGRP S_IWUSR
1108 #define S_IWOTH S_IWUSR
1111 #define S_IXGRP S_IXUSR
1114 #define S_IXOTH S_IXUSR
1117 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1122 #endif /* PLATFORM_WIN32 */
1124 /* file permissions for newly written files */
1125 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1126 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1127 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1129 #define MODE_W_PRIVATE (S_IWUSR)
1130 #define MODE_W_PUBLIC (S_IWUSR | S_IWGRP)
1131 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1133 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1134 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1136 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1137 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC)
1141 static char *dir = NULL;
1143 #if defined(PLATFORM_WIN32)
1146 dir = checked_malloc(MAX_PATH + 1);
1148 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1151 #elif defined(PLATFORM_UNIX)
1154 if ((dir = getenv("HOME")) == NULL)
1158 if ((pwd = getpwuid(getuid())) != NULL)
1159 dir = getStringCopy(pwd->pw_dir);
1171 char *getCommonDataDir(void)
1173 static char *common_data_dir = NULL;
1175 #if defined(PLATFORM_WIN32)
1176 if (common_data_dir == NULL)
1178 char *dir = checked_malloc(MAX_PATH + 1);
1180 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1181 && !strEqual(dir, "")) /* empty for Windows 95/98 */
1182 common_data_dir = getPath2(dir, program.userdata_subdir);
1184 common_data_dir = options.rw_base_directory;
1187 if (common_data_dir == NULL)
1188 common_data_dir = options.rw_base_directory;
1191 return common_data_dir;
1194 char *getPersonalDataDir(void)
1196 static char *personal_data_dir = NULL;
1198 #if defined(PLATFORM_MACOSX)
1199 if (personal_data_dir == NULL)
1200 personal_data_dir = getPath2(getHomeDir(), "Documents");
1202 if (personal_data_dir == NULL)
1203 personal_data_dir = getHomeDir();
1206 return personal_data_dir;
1209 char *getUserGameDataDir(void)
1211 static char *user_game_data_dir = NULL;
1213 if (user_game_data_dir == NULL)
1214 user_game_data_dir = getPath2(getPersonalDataDir(),
1215 program.userdata_subdir);
1217 return user_game_data_dir;
1220 void updateUserGameDataDir()
1222 #if defined(PLATFORM_MACOSX)
1223 char *userdata_dir_old = getPath2(getHomeDir(), program.userdata_subdir_unix);
1224 char *userdata_dir_new = getUserGameDataDir(); /* do not free() this */
1226 /* convert old Unix style game data directory to Mac OS X style, if needed */
1227 if (fileExists(userdata_dir_old) && !fileExists(userdata_dir_new))
1229 if (rename(userdata_dir_old, userdata_dir_new) != 0)
1231 Error(ERR_WARN, "cannot move game data directory '%s' to '%s'",
1232 userdata_dir_old, userdata_dir_new);
1234 /* continue using Unix style data directory -- this should not happen */
1235 program.userdata_path = getPath2(getPersonalDataDir(),
1236 program.userdata_subdir_unix);
1240 free(userdata_dir_old);
1246 return getUserGameDataDir();
1249 static mode_t posix_umask(mode_t mask)
1251 #if defined(PLATFORM_UNIX)
1258 static int posix_mkdir(const char *pathname, mode_t mode)
1260 #if defined(PLATFORM_WIN32)
1261 return mkdir(pathname);
1263 return mkdir(pathname, mode);
1267 void createDirectory(char *dir, char *text, int permission_class)
1269 /* leave "other" permissions in umask untouched, but ensure group parts
1270 of USERDATA_DIR_MODE are not masked */
1271 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1272 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1273 mode_t normal_umask = posix_umask(0);
1274 mode_t group_umask = ~(dir_mode & S_IRWXG);
1275 posix_umask(normal_umask & group_umask);
1277 if (!fileExists(dir))
1278 if (posix_mkdir(dir, dir_mode) != 0)
1279 Error(ERR_WARN, "cannot create %s directory '%s'", text, dir);
1281 posix_umask(normal_umask); /* reset normal umask */
1284 void InitUserDataDirectory()
1286 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1289 void SetFilePermissions(char *filename, int permission_class)
1291 chmod(filename, (permission_class == PERMS_PRIVATE ?
1292 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC));
1295 char *getCookie(char *file_type)
1297 static char cookie[MAX_COOKIE_LEN + 1];
1299 if (strlen(program.cookie_prefix) + 1 +
1300 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1301 return "[COOKIE ERROR]"; /* should never happen */
1303 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1304 program.cookie_prefix, file_type,
1305 program.version_major, program.version_minor);
1310 int getFileVersionFromCookieString(const char *cookie)
1312 const char *ptr_cookie1, *ptr_cookie2;
1313 const char *pattern1 = "_FILE_VERSION_";
1314 const char *pattern2 = "?.?";
1315 const int len_cookie = strlen(cookie);
1316 const int len_pattern1 = strlen(pattern1);
1317 const int len_pattern2 = strlen(pattern2);
1318 const int len_pattern = len_pattern1 + len_pattern2;
1319 int version_major, version_minor;
1321 if (len_cookie <= len_pattern)
1324 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1325 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1327 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1330 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1331 ptr_cookie2[1] != '.' ||
1332 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1335 version_major = ptr_cookie2[0] - '0';
1336 version_minor = ptr_cookie2[2] - '0';
1338 return VERSION_IDENT(version_major, version_minor, 0, 0);
1341 boolean checkCookieString(const char *cookie, const char *template)
1343 const char *pattern = "_FILE_VERSION_?.?";
1344 const int len_cookie = strlen(cookie);
1345 const int len_template = strlen(template);
1346 const int len_pattern = strlen(pattern);
1348 if (len_cookie != len_template)
1351 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1357 /* ------------------------------------------------------------------------- */
1358 /* setup file list and hash handling functions */
1359 /* ------------------------------------------------------------------------- */
1361 char *getFormattedSetupEntry(char *token, char *value)
1364 static char entry[MAX_LINE_LEN];
1366 /* if value is an empty string, just return token without value */
1370 /* start with the token and some spaces to format output line */
1371 sprintf(entry, "%s:", token);
1372 for (i = strlen(entry); i < token_value_position; i++)
1375 /* continue with the token's value */
1376 strcat(entry, value);
1381 SetupFileList *newSetupFileList(char *token, char *value)
1383 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1385 new->token = getStringCopy(token);
1386 new->value = getStringCopy(value);
1393 void freeSetupFileList(SetupFileList *list)
1398 checked_free(list->token);
1399 checked_free(list->value);
1402 freeSetupFileList(list->next);
1407 char *getListEntry(SetupFileList *list, char *token)
1412 if (strEqual(list->token, token))
1415 return getListEntry(list->next, token);
1418 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1423 if (strEqual(list->token, token))
1425 checked_free(list->value);
1427 list->value = getStringCopy(value);
1431 else if (list->next == NULL)
1432 return (list->next = newSetupFileList(token, value));
1434 return setListEntry(list->next, token, value);
1437 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1442 if (list->next == NULL)
1443 return (list->next = newSetupFileList(token, value));
1445 return addListEntry(list->next, token, value);
1449 static void printSetupFileList(SetupFileList *list)
1454 printf("token: '%s'\n", list->token);
1455 printf("value: '%s'\n", list->value);
1457 printSetupFileList(list->next);
1462 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1463 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1464 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1465 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1467 #define insert_hash_entry hashtable_insert
1468 #define search_hash_entry hashtable_search
1469 #define change_hash_entry hashtable_change
1470 #define remove_hash_entry hashtable_remove
1473 static unsigned int get_hash_from_key(void *key)
1478 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1479 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1480 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1481 it works better than many other constants, prime or not) has never been
1482 adequately explained.
1484 If you just want to have a good hash function, and cannot wait, djb2
1485 is one of the best string hash functions i know. It has excellent
1486 distribution and speed on many different sets of keys and table sizes.
1487 You are not likely to do better with one of the "well known" functions
1488 such as PJW, K&R, etc.
1490 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1493 char *str = (char *)key;
1494 unsigned int hash = 5381;
1497 while ((c = *str++))
1498 hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
1503 static int keys_are_equal(void *key1, void *key2)
1505 return (strEqual((char *)key1, (char *)key2));
1508 SetupFileHash *newSetupFileHash()
1510 SetupFileHash *new_hash =
1511 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1513 if (new_hash == NULL)
1514 Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1519 void freeSetupFileHash(SetupFileHash *hash)
1524 hashtable_destroy(hash, 1); /* 1 == also free values stored in hash */
1527 char *getHashEntry(SetupFileHash *hash, char *token)
1532 return search_hash_entry(hash, token);
1535 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1542 value_copy = getStringCopy(value);
1544 /* change value; if it does not exist, insert it as new */
1545 if (!change_hash_entry(hash, token, value_copy))
1546 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1547 Error(ERR_EXIT, "cannot insert into hash -- aborting");
1550 char *removeHashEntry(SetupFileHash *hash, char *token)
1555 return remove_hash_entry(hash, token);
1559 static void printSetupFileHash(SetupFileHash *hash)
1561 BEGIN_HASH_ITERATION(hash, itr)
1563 printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1564 printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1566 END_HASH_ITERATION(hash, itr)
1570 static void *loadSetupFileData(char *filename, boolean use_hash)
1572 char line[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1573 char *token, *value, *line_ptr;
1574 void *setup_file_data, *insert_ptr = NULL;
1575 boolean read_continued_line = FALSE;
1578 if (!(file = fopen(filename, MODE_READ)))
1580 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1586 setup_file_data = newSetupFileHash();
1588 insert_ptr = setup_file_data = newSetupFileList("", "");
1592 /* read next line of input file */
1593 if (!fgets(line, MAX_LINE_LEN, file))
1596 /* cut trailing newline or carriage return */
1597 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1598 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
1601 if (read_continued_line)
1603 /* cut leading whitespaces from input line */
1604 for (line_ptr = line; *line_ptr; line_ptr++)
1605 if (*line_ptr != ' ' && *line_ptr != '\t')
1608 /* append new line to existing line, if there is enough space */
1609 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
1610 strcat(previous_line, line_ptr);
1612 strcpy(line, previous_line); /* copy storage buffer to line */
1614 read_continued_line = FALSE;
1617 /* if the last character is '\', continue at next line */
1618 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
1620 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
1621 strcpy(previous_line, line); /* copy line to storage buffer */
1623 read_continued_line = TRUE;
1628 /* cut trailing comment from input line */
1629 for (line_ptr = line; *line_ptr; line_ptr++)
1631 if (*line_ptr == '#')
1638 /* cut trailing whitespaces from input line */
1639 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1640 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1643 /* ignore empty lines */
1647 /* cut leading whitespaces from token */
1648 for (token = line; *token; token++)
1649 if (*token != ' ' && *token != '\t')
1652 /* start with empty value as reliable default */
1655 /* find end of token to determine start of value */
1656 for (line_ptr = token; *line_ptr; line_ptr++)
1659 if (*line_ptr == ':' || *line_ptr == '=')
1661 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
1664 *line_ptr = '\0'; /* terminate token string */
1665 value = line_ptr + 1; /* set beginning of value */
1671 /* cut trailing whitespaces from token */
1672 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
1673 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1676 /* cut leading whitespaces from value */
1677 for (; *value; value++)
1678 if (*value != ' ' && *value != '\t')
1683 value = "true"; /* treat tokens without value as "true" */
1689 setHashEntry((SetupFileHash *)setup_file_data, token, value);
1691 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
1699 if (hashtable_count((SetupFileHash *)setup_file_data) == 0)
1700 Error(ERR_WARN, "configuration file '%s' is empty", filename);
1704 SetupFileList *setup_file_list = (SetupFileList *)setup_file_data;
1705 SetupFileList *first_valid_list_entry = setup_file_list->next;
1707 /* free empty list header */
1708 setup_file_list->next = NULL;
1709 freeSetupFileList(setup_file_list);
1710 setup_file_data = first_valid_list_entry;
1712 if (first_valid_list_entry == NULL)
1713 Error(ERR_WARN, "configuration file '%s' is empty", filename);
1716 return setup_file_data;
1719 void saveSetupFileHash(SetupFileHash *hash, char *filename)
1723 if (!(file = fopen(filename, MODE_WRITE)))
1725 Error(ERR_WARN, "cannot write configuration file '%s'", filename);
1730 BEGIN_HASH_ITERATION(hash, itr)
1732 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
1733 HASH_ITERATION_VALUE(itr)));
1735 END_HASH_ITERATION(hash, itr)
1740 SetupFileList *loadSetupFileList(char *filename)
1742 return (SetupFileList *)loadSetupFileData(filename, FALSE);
1745 SetupFileHash *loadSetupFileHash(char *filename)
1747 return (SetupFileHash *)loadSetupFileData(filename, TRUE);
1750 void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
1751 char *filename, char *identifier)
1753 char *value = getHashEntry(setup_file_hash, TOKEN_STR_FILE_IDENTIFIER);
1756 Error(ERR_WARN, "config file '%s' has no file identifier", filename);
1757 else if (!checkCookieString(value, identifier))
1758 Error(ERR_WARN, "config file '%s' has wrong file identifier", filename);
1762 /* ========================================================================= */
1763 /* setup file stuff */
1764 /* ========================================================================= */
1766 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
1767 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
1768 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
1770 /* level directory info */
1771 #define LEVELINFO_TOKEN_IDENTIFIER 0
1772 #define LEVELINFO_TOKEN_NAME 1
1773 #define LEVELINFO_TOKEN_NAME_SORTING 2
1774 #define LEVELINFO_TOKEN_AUTHOR 3
1775 #define LEVELINFO_TOKEN_IMPORTED_FROM 4
1776 #define LEVELINFO_TOKEN_IMPORTED_BY 5
1777 #define LEVELINFO_TOKEN_LEVELS 6
1778 #define LEVELINFO_TOKEN_FIRST_LEVEL 7
1779 #define LEVELINFO_TOKEN_SORT_PRIORITY 8
1780 #define LEVELINFO_TOKEN_LATEST_ENGINE 9
1781 #define LEVELINFO_TOKEN_LEVEL_GROUP 10
1782 #define LEVELINFO_TOKEN_READONLY 11
1783 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 12
1784 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 13
1785 #define LEVELINFO_TOKEN_GRAPHICS_SET 14
1786 #define LEVELINFO_TOKEN_SOUNDS_SET 15
1787 #define LEVELINFO_TOKEN_MUSIC_SET 16
1788 #define LEVELINFO_TOKEN_FILENAME 17
1789 #define LEVELINFO_TOKEN_FILETYPE 18
1790 #define LEVELINFO_TOKEN_HANDICAP 19
1791 #define LEVELINFO_TOKEN_SKIP_LEVELS 20
1793 #define NUM_LEVELINFO_TOKENS 21
1795 static LevelDirTree ldi;
1797 static struct TokenInfo levelinfo_tokens[] =
1799 /* level directory info */
1800 { TYPE_STRING, &ldi.identifier, "identifier" },
1801 { TYPE_STRING, &ldi.name, "name" },
1802 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
1803 { TYPE_STRING, &ldi.author, "author" },
1804 { TYPE_STRING, &ldi.imported_from, "imported_from" },
1805 { TYPE_STRING, &ldi.imported_by, "imported_by" },
1806 { TYPE_INTEGER, &ldi.levels, "levels" },
1807 { TYPE_INTEGER, &ldi.first_level, "first_level" },
1808 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
1809 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
1810 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
1811 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
1812 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
1813 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
1814 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
1815 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
1816 { TYPE_STRING, &ldi.music_set, "music_set" },
1817 { TYPE_STRING, &ldi.level_filename, "filename" },
1818 { TYPE_STRING, &ldi.level_filetype, "filetype" },
1819 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
1820 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" }
1823 static struct TokenInfo artworkinfo_tokens[] =
1825 /* artwork directory info */
1826 { TYPE_STRING, &ldi.identifier, "identifier" },
1827 { TYPE_STRING, &ldi.subdir, "subdir" },
1828 { TYPE_STRING, &ldi.name, "name" },
1829 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
1830 { TYPE_STRING, &ldi.author, "author" },
1831 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
1832 { TYPE_STRING, &ldi.basepath, "basepath" },
1833 { TYPE_STRING, &ldi.fullpath, "fullpath" },
1834 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
1835 { TYPE_INTEGER, &ldi.color, "color" },
1836 { TYPE_STRING, &ldi.class_desc, "class_desc" },
1841 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
1845 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
1846 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
1847 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
1848 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
1851 ti->node_parent = NULL;
1852 ti->node_group = NULL;
1859 ti->fullpath = NULL;
1860 ti->basepath = NULL;
1861 ti->identifier = NULL;
1862 ti->name = getStringCopy(ANONYMOUS_NAME);
1863 ti->name_sorting = NULL;
1864 ti->author = getStringCopy(ANONYMOUS_NAME);
1866 ti->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
1867 ti->latest_engine = FALSE; /* default: get from level */
1868 ti->parent_link = FALSE;
1869 ti->in_user_dir = FALSE;
1870 ti->user_defined = FALSE;
1872 ti->class_desc = NULL;
1874 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
1876 if (ti->type == TREE_TYPE_LEVEL_DIR)
1878 ti->imported_from = NULL;
1879 ti->imported_by = NULL;
1881 ti->graphics_set_ecs = NULL;
1882 ti->graphics_set_aga = NULL;
1883 ti->graphics_set = NULL;
1884 ti->sounds_set = NULL;
1885 ti->music_set = NULL;
1886 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
1887 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
1888 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
1890 ti->level_filename = NULL;
1891 ti->level_filetype = NULL;
1894 ti->first_level = 0;
1896 ti->level_group = FALSE;
1897 ti->handicap_level = 0;
1898 ti->readonly = TRUE;
1899 ti->handicap = TRUE;
1900 ti->skip_levels = FALSE;
1904 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
1908 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
1910 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
1915 /* copy all values from the parent structure */
1917 ti->type = parent->type;
1919 ti->node_top = parent->node_top;
1920 ti->node_parent = parent;
1921 ti->node_group = NULL;
1928 ti->fullpath = NULL;
1929 ti->basepath = NULL;
1930 ti->identifier = NULL;
1931 ti->name = getStringCopy(ANONYMOUS_NAME);
1932 ti->name_sorting = NULL;
1933 ti->author = getStringCopy(parent->author);
1935 ti->sort_priority = parent->sort_priority;
1936 ti->latest_engine = parent->latest_engine;
1937 ti->parent_link = FALSE;
1938 ti->in_user_dir = parent->in_user_dir;
1939 ti->user_defined = parent->user_defined;
1940 ti->color = parent->color;
1941 ti->class_desc = getStringCopy(parent->class_desc);
1943 ti->infotext = getStringCopy(parent->infotext);
1945 if (ti->type == TREE_TYPE_LEVEL_DIR)
1947 ti->imported_from = getStringCopy(parent->imported_from);
1948 ti->imported_by = getStringCopy(parent->imported_by);
1950 ti->graphics_set_ecs = NULL;
1951 ti->graphics_set_aga = NULL;
1952 ti->graphics_set = NULL;
1953 ti->sounds_set = NULL;
1954 ti->music_set = NULL;
1955 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
1956 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
1957 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
1959 ti->level_filename = NULL;
1960 ti->level_filetype = NULL;
1963 ti->first_level = 0;
1965 ti->level_group = FALSE;
1966 ti->handicap_level = 0;
1967 ti->readonly = TRUE;
1968 ti->handicap = TRUE;
1969 ti->skip_levels = FALSE;
1973 static void freeTreeInfo(TreeInfo *ti)
1978 checked_free(ti->subdir);
1979 checked_free(ti->fullpath);
1980 checked_free(ti->basepath);
1981 checked_free(ti->identifier);
1983 checked_free(ti->name);
1984 checked_free(ti->name_sorting);
1985 checked_free(ti->author);
1987 checked_free(ti->class_desc);
1989 checked_free(ti->infotext);
1991 if (ti->type == TREE_TYPE_LEVEL_DIR)
1993 checked_free(ti->imported_from);
1994 checked_free(ti->imported_by);
1996 checked_free(ti->graphics_set_ecs);
1997 checked_free(ti->graphics_set_aga);
1998 checked_free(ti->graphics_set);
1999 checked_free(ti->sounds_set);
2000 checked_free(ti->music_set);
2002 checked_free(ti->graphics_path);
2003 checked_free(ti->sounds_path);
2004 checked_free(ti->music_path);
2006 checked_free(ti->level_filename);
2007 checked_free(ti->level_filetype);
2013 void setSetupInfo(struct TokenInfo *token_info,
2014 int token_nr, char *token_value)
2016 int token_type = token_info[token_nr].type;
2017 void *setup_value = token_info[token_nr].value;
2019 if (token_value == NULL)
2022 /* set setup field to corresponding token value */
2027 *(boolean *)setup_value = get_boolean_from_string(token_value);
2031 *(Key *)setup_value = getKeyFromKeyName(token_value);
2035 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2039 *(int *)setup_value = get_integer_from_string(token_value);
2043 checked_free(*(char **)setup_value);
2044 *(char **)setup_value = getStringCopy(token_value);
2052 static int compareTreeInfoEntries(const void *object1, const void *object2)
2054 const TreeInfo *entry1 = *((TreeInfo **)object1);
2055 const TreeInfo *entry2 = *((TreeInfo **)object2);
2056 int class_sorting1, class_sorting2;
2059 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2061 class_sorting1 = LEVELSORTING(entry1);
2062 class_sorting2 = LEVELSORTING(entry2);
2066 class_sorting1 = ARTWORKSORTING(entry1);
2067 class_sorting2 = ARTWORKSORTING(entry2);
2070 if (entry1->parent_link || entry2->parent_link)
2071 compare_result = (entry1->parent_link ? -1 : +1);
2072 else if (entry1->sort_priority == entry2->sort_priority)
2074 char *name1 = getStringToLower(entry1->name_sorting);
2075 char *name2 = getStringToLower(entry2->name_sorting);
2077 compare_result = strcmp(name1, name2);
2082 else if (class_sorting1 == class_sorting2)
2083 compare_result = entry1->sort_priority - entry2->sort_priority;
2085 compare_result = class_sorting1 - class_sorting2;
2087 return compare_result;
2090 static void createParentTreeInfoNode(TreeInfo *node_parent)
2094 if (node_parent == NULL)
2097 ti_new = newTreeInfo();
2098 setTreeInfoToDefaults(ti_new, node_parent->type);
2100 ti_new->node_parent = node_parent;
2101 ti_new->parent_link = TRUE;
2103 setString(&ti_new->identifier, node_parent->identifier);
2104 setString(&ti_new->name, ".. (parent directory)");
2105 setString(&ti_new->name_sorting, ti_new->name);
2107 setString(&ti_new->subdir, "..");
2108 setString(&ti_new->fullpath, node_parent->fullpath);
2110 ti_new->sort_priority = node_parent->sort_priority;
2111 ti_new->latest_engine = node_parent->latest_engine;
2113 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2115 pushTreeInfo(&node_parent->node_group, ti_new);
2119 /* -------------------------------------------------------------------------- */
2120 /* functions for handling custom artwork info cache */
2121 /* -------------------------------------------------------------------------- */
2123 #define ARTWORKINFO_CACHE_FILENAME "cache.conf"
2125 static void LoadArtworkInfoCache()
2127 if (artworkinfo_hash_old == NULL)
2129 char *filename = getPath2(getSetupDir(), ARTWORKINFO_CACHE_FILENAME);
2131 /* try to load artwork info hash from already existing cache file */
2132 artworkinfo_hash_old = loadSetupFileHash(filename);
2134 /* if no artwork info cache file was found, start with empty hash */
2135 if (artworkinfo_hash_old == NULL)
2136 artworkinfo_hash_old = newSetupFileHash();
2141 if (artworkinfo_hash_new == NULL)
2142 artworkinfo_hash_new = newSetupFileHash();
2145 static void SaveArtworkInfoCache()
2147 char *filename = getPath2(getSetupDir(), ARTWORKINFO_CACHE_FILENAME);
2149 saveSetupFileHash(artworkinfo_hash_new, filename);
2154 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2156 static char *prefix = NULL;
2158 checked_free(prefix);
2160 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2165 /* (identical to above function, but separate string buffer needed -- nasty) */
2166 static char *getCacheToken(char *prefix, char *suffix)
2168 static char *token = NULL;
2170 checked_free(token);
2172 token = getStringCat2WithSeparator(prefix, suffix, ".");
2177 static char *getFileTimestamp(char *filename)
2179 struct stat file_status;
2181 if (stat(filename, &file_status) != 0) /* cannot stat file */
2182 return getStringCopy(i_to_a(0));
2184 return getStringCopy(i_to_a(file_status.st_mtime));
2187 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2189 struct stat file_status;
2191 if (timestamp_string == NULL)
2194 if (stat(filename, &file_status) != 0) /* cannot stat file */
2197 return (file_status.st_mtime != atoi(timestamp_string));
2200 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2202 char *identifier = level_node->subdir;
2203 char *type_string = ARTWORK_DIRECTORY(type);
2204 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2205 char *token_main = getCacheToken(token_prefix, "CACHED");
2206 char *cache_entry = getHashEntry(artworkinfo_hash_old, token_main);
2207 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2208 TreeInfo *artwork_info = NULL;
2211 printf("::: '%s' in cache: %d\n", token_main, cached);
2219 printf("::: LOADING existing hash entry for '%s' ...\n", identifier);
2222 artwork_info = newTreeInfo();
2223 setTreeInfoToDefaults(artwork_info, type);
2225 /* set all structure fields according to the token/value pairs */
2226 ldi = *artwork_info;
2227 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2229 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2230 char *value = getHashEntry(artworkinfo_hash_old, token);
2233 printf("::: - setting '%s' => '%s'\n", token, value);
2236 setSetupInfo(artworkinfo_tokens, i, value);
2238 /* check if cache entry for this item is invalid or incomplete */
2242 printf("::: - WARNING: cache entry '%s' invalid\n", token);
2248 *artwork_info = ldi;
2253 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2254 LEVELINFO_FILENAME);
2255 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2256 ARTWORKINFO_FILENAME(type));
2258 /* check if corresponding "levelinfo.conf" file has changed */
2259 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2260 cache_entry = getHashEntry(artworkinfo_hash_old, token_main);
2262 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2265 /* check if corresponding "<artworkinfo>.conf" file has changed */
2266 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2267 cache_entry = getHashEntry(artworkinfo_hash_old, token_main);
2269 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2274 printf("::: '%s': INVALIDATED FROM CACHE\n", identifier);
2277 checked_free(filename_levelinfo);
2278 checked_free(filename_artworkinfo);
2281 if (!cached && artwork_info != NULL)
2283 freeTreeInfo(artwork_info);
2288 return artwork_info;
2291 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
2292 LevelDirTree *level_node, int type)
2294 char *identifier = level_node->subdir;
2295 char *type_string = ARTWORK_DIRECTORY(type);
2296 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2297 char *token_main = getCacheToken(token_prefix, "CACHED");
2298 boolean set_cache_timestamps = TRUE;
2302 printf("::: adding '%s' to cache!\n", token_main);
2305 setHashEntry(artworkinfo_hash_new, token_main, "true");
2307 if (set_cache_timestamps)
2309 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2310 LEVELINFO_FILENAME);
2311 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2312 ARTWORKINFO_FILENAME(type));
2313 char *timestamp_levelinfo = getFileTimestamp(filename_levelinfo);
2314 char *timestamp_artworkinfo = getFileTimestamp(filename_artworkinfo);
2316 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2317 setHashEntry(artworkinfo_hash_new, token_main, timestamp_levelinfo);
2319 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2320 setHashEntry(artworkinfo_hash_new, token_main, timestamp_artworkinfo);
2322 checked_free(filename_levelinfo);
2323 checked_free(filename_artworkinfo);
2324 checked_free(timestamp_levelinfo);
2325 checked_free(timestamp_artworkinfo);
2328 ldi = *artwork_info;
2329 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2331 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2332 char *value = getSetupValue(artworkinfo_tokens[i].type,
2333 artworkinfo_tokens[i].value);
2336 setHashEntry(artworkinfo_hash_new, token, value);
2339 printf("::: - setting '%s' => '%s'\n\n", token, value);
2346 /* -------------------------------------------------------------------------- */
2347 /* functions for loading level info and custom artwork info */
2348 /* -------------------------------------------------------------------------- */
2350 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
2351 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
2353 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
2354 TreeInfo *node_parent,
2355 char *level_directory,
2356 char *directory_name)
2358 char *directory_path = getPath2(level_directory, directory_name);
2359 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
2360 SetupFileHash *setup_file_hash;
2361 LevelDirTree *leveldir_new = NULL;
2364 /* unless debugging, silently ignore directories without "levelinfo.conf" */
2365 if (!options.debug && !fileExists(filename))
2367 free(directory_path);
2373 setup_file_hash = loadSetupFileHash(filename);
2375 if (setup_file_hash == NULL)
2377 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
2379 free(directory_path);
2385 leveldir_new = newTreeInfo();
2388 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
2390 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
2392 leveldir_new->subdir = getStringCopy(directory_name);
2394 checkSetupFileHashIdentifier(setup_file_hash, filename,
2395 getCookie("LEVELINFO"));
2397 /* set all structure fields according to the token/value pairs */
2398 ldi = *leveldir_new;
2399 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2400 setSetupInfo(levelinfo_tokens, i,
2401 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2402 *leveldir_new = ldi;
2404 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
2405 setString(&leveldir_new->name, leveldir_new->subdir);
2407 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
2409 if (leveldir_new->identifier == NULL)
2410 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
2412 if (leveldir_new->name_sorting == NULL)
2413 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
2415 if (node_parent == NULL) /* top level group */
2417 leveldir_new->basepath = getStringCopy(level_directory);
2418 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
2420 else /* sub level group */
2422 leveldir_new->basepath = getStringCopy(node_parent->basepath);
2423 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2427 if (leveldir_new->levels < 1)
2428 leveldir_new->levels = 1;
2431 leveldir_new->last_level =
2432 leveldir_new->first_level + leveldir_new->levels - 1;
2434 leveldir_new->in_user_dir =
2435 (!strEqual(leveldir_new->basepath, options.level_directory));
2437 /* adjust some settings if user's private level directory was detected */
2438 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
2439 leveldir_new->in_user_dir &&
2440 (strEqual(leveldir_new->subdir, getLoginName()) ||
2441 strEqual(leveldir_new->name, getLoginName()) ||
2442 strEqual(leveldir_new->author, getRealName())))
2444 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
2445 leveldir_new->readonly = FALSE;
2448 leveldir_new->user_defined =
2449 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
2451 leveldir_new->color = LEVELCOLOR(leveldir_new);
2453 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
2455 leveldir_new->handicap_level = /* set handicap to default value */
2456 (leveldir_new->user_defined || !leveldir_new->handicap ?
2457 leveldir_new->last_level : leveldir_new->first_level);
2460 /* !!! don't skip sets without levels (else artwork base sets are missing) */
2462 if (leveldir_new->levels < 1 && !leveldir_new->level_group)
2464 /* skip level sets without levels (which are probably artwork base sets) */
2466 freeSetupFileHash(setup_file_hash);
2467 free(directory_path);
2475 pushTreeInfo(node_first, leveldir_new);
2477 freeSetupFileHash(setup_file_hash);
2479 if (leveldir_new->level_group)
2481 /* create node to link back to current level directory */
2482 createParentTreeInfoNode(leveldir_new);
2484 /* step into sub-directory and look for more level series */
2485 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
2486 leveldir_new, directory_path);
2489 free(directory_path);
2495 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
2496 TreeInfo *node_parent,
2497 char *level_directory)
2500 struct dirent *dir_entry;
2501 boolean valid_entry_found = FALSE;
2503 if ((dir = opendir(level_directory)) == NULL)
2505 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
2509 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
2511 struct stat file_status;
2512 char *directory_name = dir_entry->d_name;
2513 char *directory_path = getPath2(level_directory, directory_name);
2515 /* skip entries for current and parent directory */
2516 if (strEqual(directory_name, ".") ||
2517 strEqual(directory_name, ".."))
2519 free(directory_path);
2523 /* find out if directory entry is itself a directory */
2524 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
2525 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
2527 free(directory_path);
2531 free(directory_path);
2533 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
2534 strEqual(directory_name, SOUNDS_DIRECTORY) ||
2535 strEqual(directory_name, MUSIC_DIRECTORY))
2538 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2545 /* special case: top level directory may directly contain "levelinfo.conf" */
2546 if (node_parent == NULL && !valid_entry_found)
2548 /* check if this directory directly contains a file "levelinfo.conf" */
2549 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2550 level_directory, ".");
2553 if (!valid_entry_found)
2554 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
2558 boolean AdjustGraphicsForEMC()
2560 boolean settings_changed = FALSE;
2562 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
2563 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
2565 return settings_changed;
2568 void LoadLevelInfo()
2570 InitUserLevelDirectory(getLoginName());
2572 DrawInitText("Loading level series:", 120, FC_GREEN);
2574 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
2575 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
2577 /* after loading all level set information, clone the level directory tree
2578 and remove all level sets without levels (these may still contain artwork
2579 to be offered in the setup menu as "custom artwork", and are therefore
2580 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
2581 leveldir_first_all = leveldir_first;
2582 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
2584 AdjustGraphicsForEMC();
2586 /* before sorting, the first entries will be from the user directory */
2587 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2589 if (leveldir_first == NULL)
2590 Error(ERR_EXIT, "cannot find any valid level series in any directory");
2592 sortTreeInfo(&leveldir_first);
2595 dumpTreeInfo(leveldir_first, 0);
2599 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
2600 TreeInfo *node_parent,
2601 char *base_directory,
2602 char *directory_name, int type)
2604 char *directory_path = getPath2(base_directory, directory_name);
2605 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
2606 SetupFileHash *setup_file_hash = NULL;
2607 TreeInfo *artwork_new = NULL;
2610 if (fileExists(filename))
2611 setup_file_hash = loadSetupFileHash(filename);
2613 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
2616 struct dirent *dir_entry;
2617 boolean valid_file_found = FALSE;
2619 if ((dir = opendir(directory_path)) != NULL)
2621 while ((dir_entry = readdir(dir)) != NULL)
2623 char *entry_name = dir_entry->d_name;
2625 if (FileIsArtworkType(entry_name, type))
2627 valid_file_found = TRUE;
2635 if (!valid_file_found)
2637 if (!strEqual(directory_name, "."))
2638 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
2640 free(directory_path);
2647 artwork_new = newTreeInfo();
2650 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
2652 setTreeInfoToDefaults(artwork_new, type);
2654 artwork_new->subdir = getStringCopy(directory_name);
2656 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
2659 checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
2662 /* set all structure fields according to the token/value pairs */
2664 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2665 setSetupInfo(levelinfo_tokens, i,
2666 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2669 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
2670 setString(&artwork_new->name, artwork_new->subdir);
2673 DrawInitText(artwork_new->name, 150, FC_YELLOW);
2676 if (artwork_new->identifier == NULL)
2677 artwork_new->identifier = getStringCopy(artwork_new->subdir);
2679 if (artwork_new->name_sorting == NULL)
2680 artwork_new->name_sorting = getStringCopy(artwork_new->name);
2683 if (node_parent == NULL) /* top level group */
2685 artwork_new->basepath = getStringCopy(base_directory);
2686 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
2688 else /* sub level group */
2690 artwork_new->basepath = getStringCopy(node_parent->basepath);
2691 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2694 artwork_new->in_user_dir =
2695 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
2697 /* (may use ".sort_priority" from "setup_file_hash" above) */
2698 artwork_new->color = ARTWORKCOLOR(artwork_new);
2700 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
2702 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
2704 if (strEqual(artwork_new->subdir, "."))
2706 if (artwork_new->user_defined)
2708 setString(&artwork_new->identifier, "private");
2709 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
2713 setString(&artwork_new->identifier, "classic");
2714 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
2717 /* set to new values after changing ".sort_priority" */
2718 artwork_new->color = ARTWORKCOLOR(artwork_new);
2720 setString(&artwork_new->class_desc,
2721 getLevelClassDescription(artwork_new));
2725 setString(&artwork_new->identifier, artwork_new->subdir);
2728 setString(&artwork_new->name, artwork_new->identifier);
2729 setString(&artwork_new->name_sorting, artwork_new->name);
2732 DrawInitText(artwork_new->name, 150, FC_YELLOW);
2734 pushTreeInfo(node_first, artwork_new);
2736 freeSetupFileHash(setup_file_hash);
2738 free(directory_path);
2744 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
2745 TreeInfo *node_parent,
2746 char *base_directory, int type)
2749 struct dirent *dir_entry;
2750 boolean valid_entry_found = FALSE;
2752 if ((dir = opendir(base_directory)) == NULL)
2754 /* display error if directory is main "options.graphics_directory" etc. */
2755 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
2756 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
2761 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
2763 struct stat file_status;
2764 char *directory_name = dir_entry->d_name;
2765 char *directory_path = getPath2(base_directory, directory_name);
2767 /* skip directory entries for current and parent directory */
2768 if (strEqual(directory_name, ".") ||
2769 strEqual(directory_name, ".."))
2771 free(directory_path);
2775 /* skip directory entries which are not a directory or are not accessible */
2776 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
2777 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
2779 free(directory_path);
2783 free(directory_path);
2785 /* check if this directory contains artwork with or without config file */
2786 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
2788 directory_name, type);
2793 /* check if this directory directly contains artwork itself */
2794 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
2795 base_directory, ".",
2797 if (!valid_entry_found)
2798 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
2802 static TreeInfo *getDummyArtworkInfo(int type)
2804 /* this is only needed when there is completely no artwork available */
2805 TreeInfo *artwork_new = newTreeInfo();
2807 setTreeInfoToDefaults(artwork_new, type);
2809 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
2810 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
2811 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
2813 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
2814 setString(&artwork_new->name, UNDEFINED_FILENAME);
2815 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
2820 void LoadArtworkInfo()
2822 LoadArtworkInfoCache();
2824 DrawInitText("Looking for custom artwork:", 120, FC_GREEN);
2826 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
2827 options.graphics_directory,
2828 TREE_TYPE_GRAPHICS_DIR);
2829 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
2830 getUserGraphicsDir(),
2831 TREE_TYPE_GRAPHICS_DIR);
2833 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
2834 options.sounds_directory,
2835 TREE_TYPE_SOUNDS_DIR);
2836 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
2838 TREE_TYPE_SOUNDS_DIR);
2840 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
2841 options.music_directory,
2842 TREE_TYPE_MUSIC_DIR);
2843 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
2845 TREE_TYPE_MUSIC_DIR);
2847 if (artwork.gfx_first == NULL)
2848 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
2849 if (artwork.snd_first == NULL)
2850 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
2851 if (artwork.mus_first == NULL)
2852 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
2854 /* before sorting, the first entries will be from the user directory */
2855 artwork.gfx_current =
2856 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
2857 if (artwork.gfx_current == NULL)
2858 artwork.gfx_current =
2859 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
2860 if (artwork.gfx_current == NULL)
2861 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
2863 artwork.snd_current =
2864 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
2865 if (artwork.snd_current == NULL)
2866 artwork.snd_current =
2867 getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
2868 if (artwork.snd_current == NULL)
2869 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
2871 artwork.mus_current =
2872 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
2873 if (artwork.mus_current == NULL)
2874 artwork.mus_current =
2875 getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
2876 if (artwork.mus_current == NULL)
2877 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
2879 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
2880 artwork.snd_current_identifier = artwork.snd_current->identifier;
2881 artwork.mus_current_identifier = artwork.mus_current->identifier;
2884 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
2885 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
2886 printf("music set == %s\n\n", artwork.mus_current_identifier);
2889 sortTreeInfo(&artwork.gfx_first);
2890 sortTreeInfo(&artwork.snd_first);
2891 sortTreeInfo(&artwork.mus_first);
2894 dumpTreeInfo(artwork.gfx_first, 0);
2895 dumpTreeInfo(artwork.snd_first, 0);
2896 dumpTreeInfo(artwork.mus_first, 0);
2900 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
2901 LevelDirTree *level_node)
2903 int type = (*artwork_node)->type;
2905 /* recursively check all level directories for artwork sub-directories */
2909 /* check all tree entries for artwork, but skip parent link entries */
2910 if (!level_node->parent_link)
2912 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
2913 boolean cached = (artwork_new != NULL);
2917 pushTreeInfo(artwork_node, artwork_new);
2921 TreeInfo *topnode_last = *artwork_node;
2922 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
2923 ARTWORK_DIRECTORY(type));
2925 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
2927 if (topnode_last != *artwork_node) /* check for newly added node */
2929 artwork_new = *artwork_node;
2931 setString(&artwork_new->identifier, level_node->subdir);
2932 setString(&artwork_new->name, level_node->name);
2933 setString(&artwork_new->name_sorting, level_node->name_sorting);
2935 artwork_new->sort_priority = level_node->sort_priority;
2936 artwork_new->color = LEVELCOLOR(artwork_new);
2942 /* insert artwork info (from old cache or filesystem) into new cache */
2943 if (artwork_new != NULL)
2944 setArtworkInfoCacheEntry(artwork_new, level_node, type);
2947 if (level_node->node_group != NULL)
2948 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
2950 level_node = level_node->next;
2954 void LoadLevelArtworkInfo()
2956 DrawInitText("Looking for custom level artwork:", 120, FC_GREEN);
2958 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
2959 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
2960 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
2962 SaveArtworkInfoCache();
2964 /* needed for reloading level artwork not known at ealier stage */
2966 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
2968 artwork.gfx_current =
2969 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
2970 if (artwork.gfx_current == NULL)
2971 artwork.gfx_current =
2972 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
2973 if (artwork.gfx_current == NULL)
2974 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
2977 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
2979 artwork.snd_current =
2980 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
2981 if (artwork.snd_current == NULL)
2982 artwork.snd_current =
2983 getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
2984 if (artwork.snd_current == NULL)
2985 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
2988 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
2990 artwork.mus_current =
2991 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
2992 if (artwork.mus_current == NULL)
2993 artwork.mus_current =
2994 getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
2995 if (artwork.mus_current == NULL)
2996 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
2999 sortTreeInfo(&artwork.gfx_first);
3000 sortTreeInfo(&artwork.snd_first);
3001 sortTreeInfo(&artwork.mus_first);
3004 dumpTreeInfo(artwork.gfx_first, 0);
3005 dumpTreeInfo(artwork.snd_first, 0);
3006 dumpTreeInfo(artwork.mus_first, 0);
3010 static void SaveUserLevelInfo()
3012 LevelDirTree *level_info;
3017 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
3019 if (!(file = fopen(filename, MODE_WRITE)))
3021 Error(ERR_WARN, "cannot write level info file '%s'", filename);
3026 level_info = newTreeInfo();
3028 /* always start with reliable default values */
3029 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3031 setString(&level_info->name, getLoginName());
3032 setString(&level_info->author, getRealName());
3033 level_info->levels = 100;
3034 level_info->first_level = 1;
3036 token_value_position = TOKEN_VALUE_POSITION_SHORT;
3038 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3039 getCookie("LEVELINFO")));
3042 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3044 if (i == LEVELINFO_TOKEN_NAME ||
3045 i == LEVELINFO_TOKEN_AUTHOR ||
3046 i == LEVELINFO_TOKEN_LEVELS ||
3047 i == LEVELINFO_TOKEN_FIRST_LEVEL)
3048 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3050 /* just to make things nicer :) */
3051 if (i == LEVELINFO_TOKEN_AUTHOR)
3052 fprintf(file, "\n");
3055 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3059 SetFilePermissions(filename, PERMS_PRIVATE);
3061 freeTreeInfo(level_info);
3065 char *getSetupValue(int type, void *value)
3067 static char value_string[MAX_LINE_LEN];
3075 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3079 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3083 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3087 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3091 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3095 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3099 sprintf(value_string, "%d", *(int *)value);
3103 if (*(char **)value == NULL)
3106 strcpy(value_string, *(char **)value);
3110 value_string[0] = '\0';
3114 if (type & TYPE_GHOSTED)
3115 strcpy(value_string, "n/a");
3117 return value_string;
3120 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3124 static char token_string[MAX_LINE_LEN];
3125 int token_type = token_info[token_nr].type;
3126 void *setup_value = token_info[token_nr].value;
3127 char *token_text = token_info[token_nr].text;
3128 char *value_string = getSetupValue(token_type, setup_value);
3130 /* build complete token string */
3131 sprintf(token_string, "%s%s", prefix, token_text);
3133 /* build setup entry line */
3134 line = getFormattedSetupEntry(token_string, value_string);
3136 if (token_type == TYPE_KEY_X11)
3138 Key key = *(Key *)setup_value;
3139 char *keyname = getKeyNameFromKey(key);
3141 /* add comment, if useful */
3142 if (!strEqual(keyname, "(undefined)") &&
3143 !strEqual(keyname, "(unknown)"))
3145 /* add at least one whitespace */
3147 for (i = strlen(line); i < token_comment_position; i++)
3151 strcat(line, keyname);
3158 void LoadLevelSetup_LastSeries()
3160 /* ----------------------------------------------------------------------- */
3161 /* ~/.<program>/levelsetup.conf */
3162 /* ----------------------------------------------------------------------- */
3164 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3165 SetupFileHash *level_setup_hash = NULL;
3167 /* always start with reliable default values */
3168 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3170 if ((level_setup_hash = loadSetupFileHash(filename)))
3172 char *last_level_series =
3173 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3175 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3177 if (leveldir_current == NULL)
3178 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3180 checkSetupFileHashIdentifier(level_setup_hash, filename,
3181 getCookie("LEVELSETUP"));
3183 freeSetupFileHash(level_setup_hash);
3186 Error(ERR_WARN, "using default setup values");
3191 void SaveLevelSetup_LastSeries()
3193 /* ----------------------------------------------------------------------- */
3194 /* ~/.<program>/levelsetup.conf */
3195 /* ----------------------------------------------------------------------- */
3197 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3198 char *level_subdir = leveldir_current->subdir;
3201 InitUserDataDirectory();
3203 if (!(file = fopen(filename, MODE_WRITE)))
3205 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3210 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3211 getCookie("LEVELSETUP")));
3212 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
3217 SetFilePermissions(filename, PERMS_PRIVATE);
3222 static void checkSeriesInfo()
3224 static char *level_directory = NULL;
3226 struct dirent *dir_entry;
3228 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
3230 level_directory = getPath2((leveldir_current->in_user_dir ?
3231 getUserLevelDir(NULL) :
3232 options.level_directory),
3233 leveldir_current->fullpath);
3235 if ((dir = opendir(level_directory)) == NULL)
3237 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3241 while ((dir_entry = readdir(dir)) != NULL) /* last directory entry */
3243 if (strlen(dir_entry->d_name) > 4 &&
3244 dir_entry->d_name[3] == '.' &&
3245 strEqual(&dir_entry->d_name[4], LEVELFILE_EXTENSION))
3247 char levelnum_str[4];
3250 strncpy(levelnum_str, dir_entry->d_name, 3);
3251 levelnum_str[3] = '\0';
3253 levelnum_value = atoi(levelnum_str);
3256 if (levelnum_value < leveldir_current->first_level)
3258 Error(ERR_WARN, "additional level %d found", levelnum_value);
3259 leveldir_current->first_level = levelnum_value;
3261 else if (levelnum_value > leveldir_current->last_level)
3263 Error(ERR_WARN, "additional level %d found", levelnum_value);
3264 leveldir_current->last_level = levelnum_value;
3273 void LoadLevelSetup_SeriesInfo()
3276 SetupFileHash *level_setup_hash = NULL;
3277 char *level_subdir = leveldir_current->subdir;
3279 /* always start with reliable default values */
3280 level_nr = leveldir_current->first_level;
3282 checkSeriesInfo(leveldir_current);
3284 /* ----------------------------------------------------------------------- */
3285 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3286 /* ----------------------------------------------------------------------- */
3288 level_subdir = leveldir_current->subdir;
3290 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3292 if ((level_setup_hash = loadSetupFileHash(filename)))
3296 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
3300 level_nr = atoi(token_value);
3302 if (level_nr < leveldir_current->first_level)
3303 level_nr = leveldir_current->first_level;
3304 if (level_nr > leveldir_current->last_level)
3305 level_nr = leveldir_current->last_level;
3308 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
3312 int level_nr = atoi(token_value);
3314 if (level_nr < leveldir_current->first_level)
3315 level_nr = leveldir_current->first_level;
3316 if (level_nr > leveldir_current->last_level + 1)
3317 level_nr = leveldir_current->last_level;
3319 if (leveldir_current->user_defined || !leveldir_current->handicap)
3320 level_nr = leveldir_current->last_level;
3322 leveldir_current->handicap_level = level_nr;
3325 checkSetupFileHashIdentifier(level_setup_hash, filename,
3326 getCookie("LEVELSETUP"));
3328 freeSetupFileHash(level_setup_hash);
3331 Error(ERR_WARN, "using default setup values");
3336 void SaveLevelSetup_SeriesInfo()
3339 char *level_subdir = leveldir_current->subdir;
3340 char *level_nr_str = int2str(level_nr, 0);
3341 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
3344 /* ----------------------------------------------------------------------- */
3345 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3346 /* ----------------------------------------------------------------------- */
3348 InitLevelSetupDirectory(level_subdir);
3350 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3352 if (!(file = fopen(filename, MODE_WRITE)))
3354 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3359 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3360 getCookie("LEVELSETUP")));
3361 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
3363 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
3364 handicap_level_str));
3368 SetFilePermissions(filename, PERMS_PRIVATE);