-/***********************************************************
-* Artsoft Retro-Game Library *
-*----------------------------------------------------------*
-* (c) 1994-2006 Artsoft Entertainment *
-* Holger Schemel *
-* Detmolder Strasse 189 *
-* 33604 Bielefeld *
-* Germany *
-* e-mail: info@artsoft.org *
-*----------------------------------------------------------*
-* setup.c *
-***********************************************************/
+// ============================================================================
+// Artsoft Retro-Game Library
+// ----------------------------------------------------------------------------
+// (c) 1995-2014 by Artsoft Entertainment
+// Holger Schemel
+// info@artsoft.org
+// https://www.artsoft.org/
+// ----------------------------------------------------------------------------
+// setup.c
+// ============================================================================
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <string.h>
#include <unistd.h>
+#include <errno.h>
#include "platform.h"
-#if !defined(PLATFORM_WIN32)
-#include <pwd.h>
-#include <sys/param.h>
-#endif
-
#include "setup.h"
#include "joystick.h"
#include "text.h"
#include "misc.h"
#include "hash.h"
+#include "zip/miniunz.h"
+#define ENABLE_UNUSED_CODE FALSE // for currently unused functions
+#define DEBUG_NO_CONFIG_FILE FALSE // for extra-verbose debug output
+
#define NUM_LEVELCLASS_DESC 8
static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
static void setTreeInfoToDefaults(TreeInfo *, int);
+static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
static int compareTreeInfoEntries(const void *, const void *);
static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
static SetupFileHash *artworkinfo_cache_old = NULL;
static SetupFileHash *artworkinfo_cache_new = NULL;
+static SetupFileHash *optional_tokens_hash = NULL;
static boolean use_artworkinfo_cache = TRUE;
-/* ------------------------------------------------------------------------- */
-/* file functions */
-/* ------------------------------------------------------------------------- */
+// ----------------------------------------------------------------------------
+// file functions
+// ----------------------------------------------------------------------------
static char *getLevelClassDescription(TreeInfo *ti)
{
return "Unknown Level Class";
}
-static char *getUserLevelDir(char *level_subdir)
+static char *getScoreDir(char *level_subdir)
{
- static char *userlevel_dir = NULL;
- char *data_dir = getUserGameDataDir();
- char *userlevel_subdir = LEVELS_DIRECTORY;
+ static char *score_dir = NULL;
+ static char *score_level_dir = NULL;
+ char *score_subdir = SCORES_DIRECTORY;
- checked_free(userlevel_dir);
+ if (score_dir == NULL)
+ {
+ if (program.global_scores)
+ score_dir = getPath2(getCommonDataDir(), score_subdir);
+ else
+ score_dir = getPath2(getMainUserGameDataDir(), score_subdir);
+ }
if (level_subdir != NULL)
- userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
- else
- userlevel_dir = getPath2(data_dir, userlevel_subdir);
+ {
+ checked_free(score_level_dir);
- return userlevel_dir;
+ score_level_dir = getPath2(score_dir, level_subdir);
+
+ return score_level_dir;
+ }
+
+ return score_dir;
}
-static char *getScoreDir(char *level_subdir)
+static char *getUserSubdir(int nr)
{
- static char *score_dir = NULL;
- char *data_dir = getCommonDataDir();
- char *score_subdir = SCORES_DIRECTORY;
+ static char user_subdir[16] = { 0 };
- checked_free(score_dir);
+ sprintf(user_subdir, "%03d", nr);
- if (level_subdir != NULL)
- score_dir = getPath3(data_dir, score_subdir, level_subdir);
+ return user_subdir;
+}
+
+static char *getUserDir(int nr)
+{
+ static char *user_dir = NULL;
+ char *main_data_dir = getMainUserGameDataDir();
+ char *users_subdir = USERS_DIRECTORY;
+ char *user_subdir = getUserSubdir(nr);
+
+ checked_free(user_dir);
+
+ if (nr != -1)
+ user_dir = getPath3(main_data_dir, users_subdir, user_subdir);
else
- score_dir = getPath2(data_dir, score_subdir);
+ user_dir = getPath2(main_data_dir, users_subdir);
- return score_dir;
+ return user_dir;
}
static char *getLevelSetupDir(char *level_subdir)
return levelsetup_dir;
}
-static char *getCacheDir()
+static char *getCacheDir(void)
{
static char *cache_dir = NULL;
if (cache_dir == NULL)
- cache_dir = getPath2(getUserGameDataDir(), CACHE_DIRECTORY);
+ cache_dir = getPath2(getMainUserGameDataDir(), CACHE_DIRECTORY);
return cache_dir;
}
-static char *getLevelDirFromTreeInfo(TreeInfo *node)
+static char *getNetworkDir(void)
+{
+ static char *network_dir = NULL;
+
+ if (network_dir == NULL)
+ network_dir = getPath2(getMainUserGameDataDir(), NETWORK_DIRECTORY);
+
+ return network_dir;
+}
+
+char *getLevelDirFromTreeInfo(TreeInfo *node)
{
static char *level_dir = NULL;
return level_dir;
}
-char *getCurrentLevelDir()
+char *getUserLevelDir(char *level_subdir)
+{
+ static char *userlevel_dir = NULL;
+ char *data_dir = getMainUserGameDataDir();
+ char *userlevel_subdir = LEVELS_DIRECTORY;
+
+ checked_free(userlevel_dir);
+
+ if (level_subdir != NULL)
+ userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
+ else
+ userlevel_dir = getPath2(data_dir, userlevel_subdir);
+
+ return userlevel_dir;
+}
+
+char *getNetworkLevelDir(char *level_subdir)
+{
+ static char *network_level_dir = NULL;
+ char *data_dir = getNetworkDir();
+ char *networklevel_subdir = LEVELS_DIRECTORY;
+
+ checked_free(network_level_dir);
+
+ if (level_subdir != NULL)
+ network_level_dir = getPath3(data_dir, networklevel_subdir, level_subdir);
+ else
+ network_level_dir = getPath2(data_dir, networklevel_subdir);
+
+ return network_level_dir;
+}
+
+char *getCurrentLevelDir(void)
{
return getLevelDirFromTreeInfo(leveldir_current);
}
+char *getNewUserLevelSubdir(void)
+{
+ static char *new_level_subdir = NULL;
+ char *subdir_prefix = getLoginName();
+ char subdir_suffix[10];
+ int max_suffix_number = 1000;
+ int i = 0;
+
+ while (++i < max_suffix_number)
+ {
+ sprintf(subdir_suffix, "_%d", i);
+
+ checked_free(new_level_subdir);
+ new_level_subdir = getStringCat2(subdir_prefix, subdir_suffix);
+
+ if (!directoryExists(getUserLevelDir(new_level_subdir)))
+ break;
+ }
+
+ return new_level_subdir;
+}
+
static char *getTapeDir(char *level_subdir)
{
static char *tape_dir = NULL;
return tape_dir;
}
-static char *getSolutionTapeDir()
+static char *getSolutionTapeDir(void)
{
static char *tape_dir = NULL;
char *data_dir = getCurrentLevelDir();
return music_dir;
}
-static char *getDefaultArtworkSet(int type)
+static char *getClassicArtworkSet(int type)
{
return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
}
-static char *getDefaultArtworkDir(int type)
+static char *getClassicArtworkDir(int type)
{
return (type == TREE_TYPE_GRAPHICS_DIR ?
getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
}
-static char *getUserGraphicsDir()
+char *getUserGraphicsDir(void)
{
static char *usergraphics_dir = NULL;
if (usergraphics_dir == NULL)
- usergraphics_dir = getPath2(getUserGameDataDir(), GRAPHICS_DIRECTORY);
+ usergraphics_dir = getPath2(getMainUserGameDataDir(), GRAPHICS_DIRECTORY);
return usergraphics_dir;
}
-static char *getUserSoundsDir()
+char *getUserSoundsDir(void)
{
static char *usersounds_dir = NULL;
if (usersounds_dir == NULL)
- usersounds_dir = getPath2(getUserGameDataDir(), SOUNDS_DIRECTORY);
+ usersounds_dir = getPath2(getMainUserGameDataDir(), SOUNDS_DIRECTORY);
return usersounds_dir;
}
-static char *getUserMusicDir()
+char *getUserMusicDir(void)
{
static char *usermusic_dir = NULL;
if (usermusic_dir == NULL)
- usermusic_dir = getPath2(getUserGameDataDir(), MUSIC_DIRECTORY);
+ usermusic_dir = getPath2(getMainUserGameDataDir(), MUSIC_DIRECTORY);
return usermusic_dir;
}
{
static char *artwork_dir = NULL;
+ if (ti == NULL)
+ return NULL;
+
checked_free(artwork_dir);
artwork_dir = getPath2(ti->basepath, ti->fullpath);
checked_free(*artwork_path_ptr);
if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
+ {
*artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
+ }
else
{
- /* No (or non-existing) artwork configured in "levelinfo.conf". This would
- normally result in using the artwork configured in the setup menu. But
- if an artwork subdirectory exists (which might contain custom artwork
- or an artwork configuration file), this level artwork must be treated
- as relative to the default "classic" artwork, not to the artwork that
- is currently configured in the setup menu. */
+ /*
+ No (or non-existing) artwork configured in "levelinfo.conf". This would
+ normally result in using the artwork configured in the setup menu. But
+ if an artwork subdirectory exists (which might contain custom artwork
+ or an artwork configuration file), this level artwork must be treated
+ as relative to the default "classic" artwork, not to the artwork that
+ is currently configured in the setup menu.
+
+ Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
+ the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
+ the real "classic" artwork from the original R'n'D (like "gfx_classic").
+ */
char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
checked_free(*artwork_set_ptr);
- if (fileExists(dir))
+ if (directoryExists(dir))
{
- *artwork_path_ptr = getStringCopy(getDefaultArtworkDir(ti->type));
- *artwork_set_ptr = getStringCopy(getDefaultArtworkSet(ti->type));
+ *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
+ *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
}
else
{
return *artwork_set_ptr;
}
-inline static char *getLevelArtworkSet(int type)
+static char *getLevelArtworkSet(int type)
{
if (leveldir_current == NULL)
return NULL;
return LEVELDIR_ARTWORK_SET(leveldir_current, type);
}
-inline static char *getLevelArtworkDir(int type)
+static char *getLevelArtworkDir(int type)
{
if (leveldir_current == NULL)
return UNDEFINED_FILENAME;
return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
}
+char *getProgramMainDataPath(char *command_filename, char *base_path)
+{
+ // check if the program's main data base directory is configured
+ if (!strEqual(base_path, "."))
+ return getStringCopy(base_path);
+
+ /* if the program is configured to start from current directory (default),
+ determine program package directory from program binary (some versions
+ of KDE/Konqueror and Mac OS X (especially "Mavericks") apparently do not
+ set the current working directory to the program package directory) */
+ char *main_data_path = getBasePath(command_filename);
+
+#if defined(PLATFORM_MACOSX)
+ if (strSuffix(main_data_path, MAC_APP_BINARY_SUBDIR))
+ {
+ char *main_data_path_old = main_data_path;
+
+ // cut relative path to Mac OS X application binary directory from path
+ main_data_path[strlen(main_data_path) -
+ strlen(MAC_APP_BINARY_SUBDIR)] = '\0';
+
+ // cut trailing path separator from path (but not if path is root directory)
+ if (strSuffix(main_data_path, "/") && !strEqual(main_data_path, "/"))
+ main_data_path[strlen(main_data_path) - 1] = '\0';
+
+ // replace empty path with current directory
+ if (strEqual(main_data_path, ""))
+ main_data_path = ".";
+
+ // add relative path to Mac OS X application resources directory to path
+ main_data_path = getPath2(main_data_path, MAC_APP_FILES_SUBDIR);
+
+ free(main_data_path_old);
+ }
+#endif
+
+ return main_data_path;
+}
+
+char *getProgramConfigFilename(char *command_filename)
+{
+ static char *config_filename_1 = NULL;
+ static char *config_filename_2 = NULL;
+ static char *config_filename_3 = NULL;
+ static boolean initialized = FALSE;
+
+ if (!initialized)
+ {
+ char *command_filename_1 = getStringCopy(command_filename);
+
+ // strip trailing executable suffix from command filename
+ if (strSuffix(command_filename_1, ".exe"))
+ command_filename_1[strlen(command_filename_1) - 4] = '\0';
+
+ char *ro_base_path = getProgramMainDataPath(command_filename, RO_BASE_PATH);
+ char *conf_directory = getPath2(ro_base_path, CONF_DIRECTORY);
+
+ char *command_basepath = getBasePath(command_filename);
+ char *command_basename = getBaseNameNoSuffix(command_filename);
+ char *command_filename_2 = getPath2(command_basepath, command_basename);
+
+ config_filename_1 = getStringCat2(command_filename_1, ".conf");
+ config_filename_2 = getStringCat2(command_filename_2, ".conf");
+ config_filename_3 = getPath2(conf_directory, SETUP_FILENAME);
+
+ checked_free(ro_base_path);
+ checked_free(conf_directory);
+
+ checked_free(command_basepath);
+ checked_free(command_basename);
+
+ checked_free(command_filename_1);
+ checked_free(command_filename_2);
+
+ initialized = TRUE;
+ }
+
+ // 1st try: look for config file that exactly matches the binary filename
+ if (fileExists(config_filename_1))
+ return config_filename_1;
+
+ // 2nd try: look for config file that matches binary filename without suffix
+ if (fileExists(config_filename_2))
+ return config_filename_2;
+
+ // 3rd try: return setup config filename in global program config directory
+ return config_filename_3;
+}
+
char *getTapeFilename(int nr)
{
static char *filename = NULL;
sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
filename = getPath2(getSolutionTapeDir(), basename);
+ if (!fileExists(filename))
+ {
+ static char *filename_sln = NULL;
+
+ checked_free(filename_sln);
+
+ sprintf(basename, "%03d.sln", nr);
+ filename_sln = getPath2(getSolutionTapeDir(), basename);
+
+ if (fileExists(filename_sln))
+ return filename_sln;
+ }
+
return filename;
}
checked_free(filename);
sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
- filename = getPath2(getScoreDir(leveldir_current->subdir), basename);
+
+ // used instead of "leveldir_current->subdir" (for network games)
+ filename = getPath2(getScoreDir(levelset.identifier), basename);
return filename;
}
-char *getSetupFilename()
+char *getSetupFilename(void)
{
static char *filename = NULL;
return filename;
}
-char *getEditorSetupFilename()
+char *getDefaultSetupFilename(void)
+{
+ return program.config_filename;
+}
+
+char *getEditorSetupFilename(void)
{
static char *filename = NULL;
return filename;
}
-char *getHelpAnimFilename()
+char *getHelpAnimFilename(void)
{
static char *filename = NULL;
return filename;
}
-char *getHelpTextFilename()
+char *getHelpTextFilename(void)
{
static char *filename = NULL;
return filename;
}
-char *getLevelSetInfoFilename()
+char *getLevelSetInfoFilename(void)
{
static char *filename = NULL;
char *basenames[] =
return NULL;
}
-static char *getCorrectedArtworkBasename(char *basename)
+static char *getLevelSetTitleMessageBasename(int nr, boolean initial)
+{
+ static char basename[32];
+
+ sprintf(basename, "%s_%d.txt",
+ (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
+
+ return basename;
+}
+
+char *getLevelSetTitleMessageFilename(int nr, boolean initial)
{
- char *basename_corrected = basename;
+ static char *filename = NULL;
+ char *basename;
+ boolean skip_setup_artwork = FALSE;
+
+ checked_free(filename);
+
+ basename = getLevelSetTitleMessageBasename(nr, initial);
-#if defined(PLATFORM_MSDOS)
- if (program.filename_prefix != NULL)
+ if (!gfx.override_level_graphics)
{
- int prefix_len = strlen(program.filename_prefix);
+ // 1st try: look for special artwork in current level series directory
+ filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
+ if (fileExists(filename))
+ return filename;
+
+ free(filename);
- if (strncmp(basename, program.filename_prefix, prefix_len) == 0)
- basename_corrected = &basename[prefix_len];
+ // 2nd try: look for message file in current level set directory
+ filename = getPath2(getCurrentLevelDir(), basename);
+ if (fileExists(filename))
+ return filename;
- /* if corrected filename is still longer than standard MS-DOS filename
- size (8 characters + 1 dot + 3 characters file extension), shorten
- filename by writing file extension after 8th basename character */
- if (strlen(basename_corrected) > 8 + 1 + 3)
- {
- static char *msdos_filename = NULL;
+ free(filename);
- checked_free(msdos_filename);
+ // check if there is special artwork configured in level series config
+ if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
+ {
+ // 3rd try: look for special artwork configured in level series config
+ filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
+ if (fileExists(filename))
+ return filename;
- msdos_filename = getStringCopy(basename_corrected);
- strncpy(&msdos_filename[8], &basename[strlen(basename) - (1+3)], 1+3 +1);
+ free(filename);
- basename_corrected = msdos_filename;
+ // take missing artwork configured in level set config from default
+ skip_setup_artwork = TRUE;
}
}
-#endif
- return basename_corrected;
+ if (!skip_setup_artwork)
+ {
+ // 4th try: look for special artwork in configured artwork directory
+ filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
+ if (fileExists(filename))
+ return filename;
+
+ free(filename);
+ }
+
+ // 5th try: look for default artwork in new default artwork directory
+ filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
+ if (fileExists(filename))
+ return filename;
+
+ free(filename);
+
+ // 6th try: look for default artwork in old default artwork directory
+ filename = getPath2(options.graphics_directory, basename);
+ if (fileExists(filename))
+ return filename;
+
+ return NULL; // cannot find specified artwork file anywhere
+}
+
+static char *getCorrectedArtworkBasename(char *basename)
+{
+ return basename;
}
char *getCustomImageFilename(char *basename)
basename = getCorrectedArtworkBasename(basename);
- if (!setup.override_level_graphics)
+ if (!gfx.override_level_graphics)
{
- /* 1st try: look for special artwork in current level series directory */
- filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
+ // 1st try: look for special artwork in current level series directory
+ filename = getImg3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
if (fileExists(filename))
return filename;
free(filename);
- /* check if there is special artwork configured in level series config */
+ // check if there is special artwork configured in level series config
if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
{
- /* 2nd try: look for special artwork configured in level series config */
- filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
+ // 2nd try: look for special artwork configured in level series config
+ filename = getImg2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
if (fileExists(filename))
return filename;
free(filename);
- /* take missing artwork configured in level set config from default */
+ // take missing artwork configured in level set config from default
skip_setup_artwork = TRUE;
}
}
if (!skip_setup_artwork)
{
- /* 3rd try: look for special artwork in configured artwork directory */
- filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
+ // 3rd try: look for special artwork in configured artwork directory
+ filename = getImg2(getSetupArtworkDir(artwork.gfx_current), basename);
if (fileExists(filename))
return filename;
free(filename);
}
- /* 4th try: look for default artwork in new default artwork directory */
- filename = getPath2(getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR), basename);
+ // 4th try: look for default artwork in new default artwork directory
+ filename = getImg2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
if (fileExists(filename))
return filename;
free(filename);
- /* 5th try: look for default artwork in old default artwork directory */
- filename = getPath2(options.graphics_directory, basename);
+ // 5th try: look for default artwork in old default artwork directory
+ filename = getImg2(options.graphics_directory, basename);
if (fileExists(filename))
return filename;
- return NULL; /* cannot find specified artwork file anywhere */
+ if (!strEqual(GFX_FALLBACK_FILENAME, UNDEFINED_FILENAME))
+ {
+ free(filename);
+
+ Warn("cannot find artwork file '%s' (using fallback)", basename);
+
+ // 6th try: look for fallback artwork in old default artwork directory
+ // (needed to prevent errors when trying to access unused artwork files)
+ filename = getImg2(options.graphics_directory, GFX_FALLBACK_FILENAME);
+ if (fileExists(filename))
+ return filename;
+ }
+
+ return NULL; // cannot find specified artwork file anywhere
}
char *getCustomSoundFilename(char *basename)
basename = getCorrectedArtworkBasename(basename);
- if (!setup.override_level_sounds)
+ if (!gfx.override_level_sounds)
{
- /* 1st try: look for special artwork in current level series directory */
+ // 1st try: look for special artwork in current level series directory
filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
if (fileExists(filename))
return filename;
free(filename);
- /* check if there is special artwork configured in level series config */
+ // check if there is special artwork configured in level series config
if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
{
- /* 2nd try: look for special artwork configured in level series config */
+ // 2nd try: look for special artwork configured in level series config
filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
if (fileExists(filename))
return filename;
free(filename);
- /* take missing artwork configured in level set config from default */
+ // take missing artwork configured in level set config from default
skip_setup_artwork = TRUE;
}
}
if (!skip_setup_artwork)
{
- /* 3rd try: look for special artwork in configured artwork directory */
+ // 3rd try: look for special artwork in configured artwork directory
filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
if (fileExists(filename))
return filename;
free(filename);
}
- /* 4th try: look for default artwork in new default artwork directory */
- filename = getPath2(getDefaultSoundsDir(SND_CLASSIC_SUBDIR), basename);
+ // 4th try: look for default artwork in new default artwork directory
+ filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
if (fileExists(filename))
return filename;
free(filename);
- /* 5th try: look for default artwork in old default artwork directory */
+ // 5th try: look for default artwork in old default artwork directory
filename = getPath2(options.sounds_directory, basename);
if (fileExists(filename))
return filename;
- return NULL; /* cannot find specified artwork file anywhere */
+ if (!strEqual(SND_FALLBACK_FILENAME, UNDEFINED_FILENAME))
+ {
+ free(filename);
+
+ Warn("cannot find artwork file '%s' (using fallback)", basename);
+
+ // 6th try: look for fallback artwork in old default artwork directory
+ // (needed to prevent errors when trying to access unused artwork files)
+ filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
+ if (fileExists(filename))
+ return filename;
+ }
+
+ return NULL; // cannot find specified artwork file anywhere
}
char *getCustomMusicFilename(char *basename)
basename = getCorrectedArtworkBasename(basename);
- if (!setup.override_level_music)
+ if (!gfx.override_level_music)
{
- /* 1st try: look for special artwork in current level series directory */
+ // 1st try: look for special artwork in current level series directory
filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
if (fileExists(filename))
return filename;
free(filename);
- /* check if there is special artwork configured in level series config */
+ // check if there is special artwork configured in level series config
if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
{
- /* 2nd try: look for special artwork configured in level series config */
+ // 2nd try: look for special artwork configured in level series config
filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
if (fileExists(filename))
return filename;
free(filename);
- /* take missing artwork configured in level set config from default */
+ // take missing artwork configured in level set config from default
skip_setup_artwork = TRUE;
}
}
if (!skip_setup_artwork)
{
- /* 3rd try: look for special artwork in configured artwork directory */
+ // 3rd try: look for special artwork in configured artwork directory
filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
if (fileExists(filename))
return filename;
free(filename);
}
- /* 4th try: look for default artwork in new default artwork directory */
- filename = getPath2(getDefaultMusicDir(MUS_CLASSIC_SUBDIR), basename);
+ // 4th try: look for default artwork in new default artwork directory
+ filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
if (fileExists(filename))
return filename;
free(filename);
- /* 5th try: look for default artwork in old default artwork directory */
+ // 5th try: look for default artwork in old default artwork directory
filename = getPath2(options.music_directory, basename);
if (fileExists(filename))
return filename;
- return NULL; /* cannot find specified artwork file anywhere */
+ if (!strEqual(MUS_FALLBACK_FILENAME, UNDEFINED_FILENAME))
+ {
+ free(filename);
+
+ Warn("cannot find artwork file '%s' (using fallback)", basename);
+
+ // 6th try: look for fallback artwork in old default artwork directory
+ // (needed to prevent errors when trying to access unused artwork files)
+ filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
+ if (fileExists(filename))
+ return filename;
+ }
+
+ return NULL; // cannot find specified artwork file anywhere
}
char *getCustomArtworkFilename(char *basename, int type)
checked_free(directory);
- if (!setup.override_level_music)
+ if (!gfx.override_level_music)
{
- /* 1st try: look for special artwork in current level series directory */
+ // 1st try: look for special artwork in current level series directory
directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
- if (fileExists(directory))
+ if (directoryExists(directory))
return directory;
free(directory);
- /* check if there is special artwork configured in level series config */
+ // check if there is special artwork configured in level series config
if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
{
- /* 2nd try: look for special artwork configured in level series config */
+ // 2nd try: look for special artwork configured in level series config
directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
- if (fileExists(directory))
+ if (directoryExists(directory))
return directory;
free(directory);
- /* take missing artwork configured in level set config from default */
+ // take missing artwork configured in level set config from default
skip_setup_artwork = TRUE;
}
}
if (!skip_setup_artwork)
{
- /* 3rd try: look for special artwork in configured artwork directory */
+ // 3rd try: look for special artwork in configured artwork directory
directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
- if (fileExists(directory))
+ if (directoryExists(directory))
return directory;
free(directory);
}
- /* 4th try: look for default artwork in new default artwork directory */
- directory = getStringCopy(getDefaultMusicDir(MUS_CLASSIC_SUBDIR));
- if (fileExists(directory))
+ // 4th try: look for default artwork in new default artwork directory
+ directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
+ if (directoryExists(directory))
return directory;
free(directory);
- /* 5th try: look for default artwork in old default artwork directory */
+ // 5th try: look for default artwork in old default artwork directory
directory = getStringCopy(options.music_directory);
- if (fileExists(directory))
+ if (directoryExists(directory))
return directory;
- return NULL; /* cannot find specified artwork file anywhere */
+ return NULL; // cannot find specified artwork file anywhere
}
void InitTapeDirectory(char *level_subdir)
void InitScoreDirectory(char *level_subdir)
{
- createDirectory(getCommonDataDir(), "common data", PERMS_PUBLIC);
- createDirectory(getScoreDir(NULL), "main score", PERMS_PUBLIC);
- createDirectory(getScoreDir(level_subdir), "level score", PERMS_PUBLIC);
+ int permissions = (program.global_scores ? PERMS_PUBLIC : PERMS_PRIVATE);
+
+ if (program.global_scores)
+ createDirectory(getCommonDataDir(), "common data", permissions);
+ else
+ createDirectory(getMainUserGameDataDir(), "main user data", permissions);
+
+ createDirectory(getScoreDir(NULL), "main score", permissions);
+ createDirectory(getScoreDir(level_subdir), "level score", permissions);
}
-static void SaveUserLevelInfo();
+static void SaveUserLevelInfo(void);
void InitUserLevelDirectory(char *level_subdir)
{
- if (!fileExists(getUserLevelDir(level_subdir)))
+ if (!directoryExists(getUserLevelDir(level_subdir)))
{
- createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
+ createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
- SaveUserLevelInfo();
+ if (setup.internal.create_user_levelset)
+ SaveUserLevelInfo();
+ }
+}
+
+void InitNetworkLevelDirectory(char *level_subdir)
+{
+ if (!directoryExists(getNetworkLevelDir(level_subdir)))
+ {
+ createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
+ createDirectory(getNetworkDir(), "network data", PERMS_PRIVATE);
+ createDirectory(getNetworkLevelDir(NULL), "main network level", PERMS_PRIVATE);
+ createDirectory(getNetworkLevelDir(level_subdir), "network level", PERMS_PRIVATE);
}
}
createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
}
-void InitCacheDirectory()
+static void InitCacheDirectory(void)
{
- createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
+ createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
}
-/* ------------------------------------------------------------------------- */
-/* some functions to handle lists of level and artwork directories */
-/* ------------------------------------------------------------------------- */
+// ----------------------------------------------------------------------------
+// some functions to handle lists of level and artwork directories
+// ----------------------------------------------------------------------------
-TreeInfo *newTreeInfo()
+TreeInfo *newTreeInfo(void)
{
return checked_calloc(sizeof(TreeInfo));
}
if (node == NULL)
return NULL;
- if (node->node_group) /* enter level group (step down into tree) */
+ if (node->node_group) // enter level group (step down into tree)
return getFirstValidTreeInfoEntry(node->node_group);
- else if (node->parent_link) /* skip start entry of level group */
+ else if (node->parent_link) // skip start entry of level group
{
- if (node->next) /* get first real level series entry */
+ if (node->next) // get first real level series entry
return getFirstValidTreeInfoEntry(node->next);
- else /* leave empty level group and go on */
+ else // leave empty level group and go on
return getFirstValidTreeInfoEntry(node->node_parent->next);
}
- else /* this seems to be a regular level series */
+ else // this seems to be a regular level series
return node;
}
if (node == NULL)
return NULL;
- if (node->node_parent == NULL) /* top level group */
+ if (node->node_parent == NULL) // top level group
return *node->node_top;
- else /* sub level group */
+ else // sub level group
return node->node_parent->node_group;
}
return numTreeInfo(getTreeInfoFirstGroupEntry(node));
}
-int posTreeInfo(TreeInfo *node)
+int getPosFromTreeInfo(TreeInfo *node)
{
TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
int pos = 0;
return NULL;
}
-TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
- TreeInfo *node, boolean skip_sets_without_levels)
+static TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
+ TreeInfo *node, boolean skip_sets_without_levels)
{
TreeInfo *node_new;
return cloneTreeNode(node_top, node_parent, node->next,
skip_sets_without_levels);
- node_new = newTreeInfo();
+ node_new = getTreeInfoCopy(node); // copy complete node
- *node_new = *node; /* copy complete node */
-
- node_new->node_top = node_top; /* correct top node link */
- node_new->node_parent = node_parent; /* correct parent node link */
+ node_new->node_top = node_top; // correct top node link
+ node_new->node_parent = node_parent; // correct parent node link
if (node->level_group)
node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
return node_new;
}
-void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
+static void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
{
TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
while (node)
{
- if (node->graphics_set_ecs && !setup.prefer_aga_graphics &&
- !strEqual(node->graphics_set, node->graphics_set_ecs))
+ boolean want_ecs = (setup.prefer_aga_graphics == FALSE);
+ boolean want_aga = (setup.prefer_aga_graphics == TRUE);
+ boolean has_only_ecs = (!node->graphics_set && !node->graphics_set_aga);
+ boolean has_only_aga = (!node->graphics_set && !node->graphics_set_ecs);
+ char *graphics_set = NULL;
+
+ if (node->graphics_set_ecs && (want_ecs || has_only_ecs))
+ graphics_set = node->graphics_set_ecs;
+
+ if (node->graphics_set_aga && (want_aga || has_only_aga))
+ graphics_set = node->graphics_set_aga;
+
+ if (graphics_set && !strEqual(node->graphics_set, graphics_set))
{
- setString(&node->graphics_set, node->graphics_set_ecs);
+ setString(&node->graphics_set, graphics_set);
settings_changed = TRUE;
}
- else if (node->graphics_set_aga && setup.prefer_aga_graphics &&
- !strEqual(node->graphics_set, node->graphics_set_aga))
+
+ if (node->node_group != NULL)
+ settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
+
+ node = node->next;
+ }
+
+ return settings_changed;
+}
+
+static boolean adjustTreeSoundsForEMC(TreeInfo *node)
+{
+ boolean settings_changed = FALSE;
+
+ while (node)
+ {
+ boolean want_default = (setup.prefer_lowpass_sounds == FALSE);
+ boolean want_lowpass = (setup.prefer_lowpass_sounds == TRUE);
+ boolean has_only_default = (!node->sounds_set && !node->sounds_set_lowpass);
+ boolean has_only_lowpass = (!node->sounds_set && !node->sounds_set_default);
+ char *sounds_set = NULL;
+
+ if (node->sounds_set_default && (want_default || has_only_default))
+ sounds_set = node->sounds_set_default;
+
+ if (node->sounds_set_lowpass && (want_lowpass || has_only_lowpass))
+ sounds_set = node->sounds_set_lowpass;
+
+ if (sounds_set && !strEqual(node->sounds_set, sounds_set))
{
- setString(&node->graphics_set, node->graphics_set_aga);
+ setString(&node->sounds_set, sounds_set);
settings_changed = TRUE;
}
if (node->node_group != NULL)
- settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
+ settings_changed |= adjustTreeSoundsForEMC(node->node_group);
node = node->next;
}
{
int i;
- printf("Dumping TreeInfo:\n");
+ Debug("tree", "Dumping TreeInfo:");
while (node)
{
for (i = 0; i < (depth + 1) * 3; i++)
- printf(" ");
+ DebugContinued("", " ");
+
+ DebugContinued("tree", "'%s' / '%s'\n", node->identifier, node->name);
- printf("subdir == '%s' ['%s', '%s'] [%d])\n",
- node->subdir, node->fullpath, node->basepath, node->in_user_dir);
+ /*
+ // use for dumping artwork info tree
+ Debug("tree", "subdir == '%s' ['%s', '%s'] [%d])",
+ node->subdir, node->fullpath, node->basepath, node->in_user_dir);
+ */
if (node->node_group != NULL)
dumpTreeInfo(node->node_group, depth + 1);
if (num_nodes == 0)
return;
- /* allocate array for sorting structure pointers */
+ // allocate array for sorting structure pointers
sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
- /* writing structure pointers to sorting array */
- while (i < num_nodes && node) /* double boundary check... */
+ // writing structure pointers to sorting array
+ while (i < num_nodes && node) // double boundary check...
{
sort_array[i] = node;
node = node->next;
}
- /* sorting the structure pointers in the sorting array */
+ // sorting the structure pointers in the sorting array
qsort(sort_array, num_nodes, sizeof(TreeInfo *),
compare_function);
- /* update the linkage of list elements with the sorted node array */
+ // update the linkage of list elements with the sorted node array
for (i = 0; i < num_nodes - 1; i++)
sort_array[i]->next = sort_array[i + 1];
sort_array[num_nodes - 1]->next = NULL;
- /* update the linkage of the main list anchor pointer */
+ // update the linkage of the main list anchor pointer
*node_first = sort_array[0];
free(sort_array);
- /* now recursively sort the level group structures */
+ // now recursively sort the level group structures
node = *node_first;
while (node)
{
}
-/* ========================================================================= */
-/* some stuff from "files.c" */
-/* ========================================================================= */
+// ============================================================================
+// some stuff from "files.c"
+// ============================================================================
#if defined(PLATFORM_WIN32)
#ifndef S_IRGRP
#ifndef S_ISGID
#define S_ISGID 0
#endif
-#endif /* PLATFORM_WIN32 */
+#endif // PLATFORM_WIN32
-/* file permissions for newly written files */
+// file permissions for newly written files
#define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
#define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
#define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
#define MODE_W_PRIVATE (S_IWUSR)
-#define MODE_W_PUBLIC (S_IWUSR | S_IWGRP)
+#define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
#define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
#define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
#define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
+#define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
#define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
-#define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC)
+#define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
+#define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
-char *getHomeDir()
+
+char *getHomeDir(void)
{
static char *dir = NULL;
{
if ((dir = getenv("HOME")) == NULL)
{
- struct passwd *pwd;
+ dir = getUnixHomeDir();
- if ((pwd = getpwuid(getuid())) != NULL)
- dir = getStringCopy(pwd->pw_dir);
+ if (dir != NULL)
+ dir = getStringCopy(dir);
else
dir = ".";
}
char *dir = checked_malloc(MAX_PATH + 1);
if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
- && !strEqual(dir, "")) /* empty for Windows 95/98 */
+ && !strEqual(dir, "")) // empty for Windows 95/98
common_data_dir = getPath2(dir, program.userdata_subdir);
else
common_data_dir = options.rw_base_directory;
return personal_data_dir;
}
-char *getUserGameDataDir(void)
+char *getMainUserGameDataDir(void)
{
- static char *user_game_data_dir = NULL;
+ static char *main_user_data_dir = NULL;
- if (user_game_data_dir == NULL)
- user_game_data_dir = getPath2(getPersonalDataDir(),
+#if defined(PLATFORM_ANDROID)
+ if (main_user_data_dir == NULL)
+ main_user_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
+ SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
+ SDL_AndroidGetExternalStoragePath() :
+ SDL_AndroidGetInternalStoragePath());
+#else
+ if (main_user_data_dir == NULL)
+ main_user_data_dir = getPath2(getPersonalDataDir(),
program.userdata_subdir);
+#endif
- return user_game_data_dir;
+ return main_user_data_dir;
}
-void updateUserGameDataDir()
+char *getUserGameDataDir(void)
{
-#if defined(PLATFORM_MACOSX)
- char *userdata_dir_old = getPath2(getHomeDir(), program.userdata_subdir_unix);
- char *userdata_dir_new = getUserGameDataDir(); /* do not free() this */
-
- /* convert old Unix style game data directory to Mac OS X style, if needed */
- if (fileExists(userdata_dir_old) && !fileExists(userdata_dir_new))
- {
- if (rename(userdata_dir_old, userdata_dir_new) != 0)
- {
- Error(ERR_WARN, "cannot move game data directory '%s' to '%s'",
- userdata_dir_old, userdata_dir_new);
-
- /* continue using Unix style data directory -- this should not happen */
- program.userdata_path = getPath2(getPersonalDataDir(),
- program.userdata_subdir_unix);
- }
- }
-
- free(userdata_dir_old);
-#endif
+ if (user.nr == 0)
+ return getMainUserGameDataDir();
+ else
+ return getUserDir(user.nr);
}
-char *getSetupDir()
+char *getSetupDir(void)
{
return getUserGameDataDir();
}
#endif
}
+static boolean posix_process_running_setgid(void)
+{
+#if defined(PLATFORM_UNIX)
+ return (getgid() != getegid());
+#else
+ return FALSE;
+#endif
+}
+
void createDirectory(char *dir, char *text, int permission_class)
{
- /* leave "other" permissions in umask untouched, but ensure group parts
- of USERDATA_DIR_MODE are not masked */
+ if (directoryExists(dir))
+ return;
+
+ // leave "other" permissions in umask untouched, but ensure group parts
+ // of USERDATA_DIR_MODE are not masked
mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
- mode_t normal_umask = posix_umask(0);
+ mode_t last_umask = posix_umask(0);
mode_t group_umask = ~(dir_mode & S_IRWXG);
- posix_umask(normal_umask & group_umask);
+ int running_setgid = posix_process_running_setgid();
- if (!fileExists(dir))
- if (posix_mkdir(dir, dir_mode) != 0)
- Error(ERR_WARN, "cannot create %s directory '%s'", text, dir);
+ if (permission_class == PERMS_PUBLIC)
+ {
+ // if we're setgid, protect files against "other"
+ // else keep umask(0) to make the dir world-writable
- posix_umask(normal_umask); /* reset normal umask */
-}
+ if (running_setgid)
+ posix_umask(last_umask & group_umask);
+ else
+ dir_mode = DIR_PERMS_PUBLIC_ALL;
+ }
-void InitUserDataDirectory()
-{
- createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
+ if (posix_mkdir(dir, dir_mode) != 0)
+ Warn("cannot create %s directory '%s': %s", text, dir, strerror(errno));
+
+ if (permission_class == PERMS_PUBLIC && !running_setgid)
+ chmod(dir, dir_mode);
+
+ posix_umask(last_umask); // restore previous umask
+}
+
+void InitMainUserDataDirectory(void)
+{
+ createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
+}
+
+void InitUserDataDirectory(void)
+{
+ createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
+
+ if (user.nr != 0)
+ {
+ createDirectory(getUserDir(-1), "users", PERMS_PRIVATE);
+ createDirectory(getUserDir(user.nr), "user data", PERMS_PRIVATE);
+ }
}
void SetFilePermissions(char *filename, int permission_class)
{
- chmod(filename, (permission_class == PERMS_PRIVATE ?
- FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC));
+ int running_setgid = posix_process_running_setgid();
+ int perms = (permission_class == PERMS_PRIVATE ?
+ FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
+
+ if (permission_class == PERMS_PUBLIC && !running_setgid)
+ perms = FILE_PERMS_PUBLIC_ALL;
+
+ chmod(filename, perms);
}
char *getCookie(char *file_type)
if (strlen(program.cookie_prefix) + 1 +
strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
- return "[COOKIE ERROR]"; /* should never happen */
+ return "[COOKIE ERROR]"; // should never happen
sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
program.cookie_prefix, file_type,
- program.version_major, program.version_minor);
+ program.version_super, program.version_major);
return cookie;
}
+void fprintFileHeader(FILE *file, char *basename)
+{
+ char *prefix = "# ";
+ char *sep1 = "=";
+
+ fprintf_line_with_prefix(file, prefix, sep1, 77);
+ fprintf(file, "%s%s\n", prefix, basename);
+ fprintf_line_with_prefix(file, prefix, sep1, 77);
+ fprintf(file, "\n");
+}
+
int getFileVersionFromCookieString(const char *cookie)
{
const char *ptr_cookie1, *ptr_cookie2;
const int len_pattern1 = strlen(pattern1);
const int len_pattern2 = strlen(pattern2);
const int len_pattern = len_pattern1 + len_pattern2;
- int version_major, version_minor;
+ int version_super, version_major;
if (len_cookie <= len_pattern)
return -1;
ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
return -1;
- version_major = ptr_cookie2[0] - '0';
- version_minor = ptr_cookie2[2] - '0';
+ version_super = ptr_cookie2[0] - '0';
+ version_major = ptr_cookie2[2] - '0';
- return VERSION_IDENT(version_major, version_minor, 0, 0);
+ return VERSION_IDENT(version_super, version_major, 0, 0);
}
boolean checkCookieString(const char *cookie, const char *template)
return TRUE;
}
-/* ------------------------------------------------------------------------- */
-/* setup file list and hash handling functions */
-/* ------------------------------------------------------------------------- */
+
+// ----------------------------------------------------------------------------
+// setup file list and hash handling functions
+// ----------------------------------------------------------------------------
char *getFormattedSetupEntry(char *token, char *value)
{
int i;
static char entry[MAX_LINE_LEN];
- /* if value is an empty string, just return token without value */
+ // if value is an empty string, just return token without value
if (*value == '\0')
return token;
- /* start with the token and some spaces to format output line */
+ // start with the token and some spaces to format output line
sprintf(entry, "%s:", token);
for (i = strlen(entry); i < token_value_position; i++)
strcat(entry, " ");
- /* continue with the token's value */
+ // continue with the token's value
strcat(entry, value);
return entry;
return addListEntry(list->next, token, value);
}
+#if ENABLE_UNUSED_CODE
#ifdef DEBUG
static void printSetupFileList(SetupFileList *list)
{
if (!list)
return;
- printf("token: '%s'\n", list->token);
- printf("value: '%s'\n", list->value);
+ Debug("setup:printSetupFileList", "token: '%s'", list->token);
+ Debug("setup:printSetupFileList", "value: '%s'", list->value);
printSetupFileList(list->next);
}
#endif
+#endif
#ifdef DEBUG
DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
#define remove_hash_entry hashtable_remove
#endif
-static unsigned int get_hash_from_key(void *key)
+unsigned int get_hash_from_key(void *key)
{
/*
djb2
int c;
while ((c = *str++))
- hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
+ hash = ((hash << 5) + hash) + c; // hash * 33 + c
return hash;
}
return (strEqual((char *)key1, (char *)key2));
}
-SetupFileHash *newSetupFileHash()
+SetupFileHash *newSetupFileHash(void)
{
SetupFileHash *new_hash =
create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
if (new_hash == NULL)
- Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
+ Fail("create_hashtable() failed -- out of memory");
return new_hash;
}
if (hash == NULL)
return;
- hashtable_destroy(hash, 1); /* 1 == also free values stored in hash */
+ hashtable_destroy(hash, 1); // 1 == also free values stored in hash
}
char *getHashEntry(SetupFileHash *hash, char *token)
value_copy = getStringCopy(value);
- /* change value; if it does not exist, insert it as new */
+ // change value; if it does not exist, insert it as new
if (!change_hash_entry(hash, token, value_copy))
if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
- Error(ERR_EXIT, "cannot insert into hash -- aborting");
+ Fail("cannot insert into hash -- aborting");
}
char *removeHashEntry(SetupFileHash *hash, char *token)
return remove_hash_entry(hash, token);
}
-#if 0
+#if ENABLE_UNUSED_CODE
+#if DEBUG
static void printSetupFileHash(SetupFileHash *hash)
{
BEGIN_HASH_ITERATION(hash, itr)
{
- printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
- printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
+ Debug("setup:printSetupFileHash", "token: '%s'", HASH_ITERATION_TOKEN(itr));
+ Debug("setup:printSetupFileHash", "value: '%s'", HASH_ITERATION_VALUE(itr));
}
END_HASH_ITERATION(hash, itr)
}
+#endif
+#endif
+
+#define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
+#define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
+#define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
+
+static boolean token_value_separator_found = FALSE;
+#if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
+static boolean token_value_separator_warning = FALSE;
+#endif
+#if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
+static boolean token_already_exists_warning = FALSE;
+#endif
+
+static boolean getTokenValueFromSetupLineExt(char *line,
+ char **token_ptr, char **value_ptr,
+ char *filename, char *line_raw,
+ int line_nr,
+ boolean separator_required)
+{
+ static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
+ char *token, *value, *line_ptr;
+
+ // when externally invoked via ReadTokenValueFromLine(), copy line buffers
+ if (line_raw == NULL)
+ {
+ strncpy(line_copy, line, MAX_LINE_LEN);
+ line_copy[MAX_LINE_LEN] = '\0';
+ line = line_copy;
+
+ strcpy(line_raw_copy, line_copy);
+ line_raw = line_raw_copy;
+ }
+
+ // cut trailing comment from input line
+ for (line_ptr = line; *line_ptr; line_ptr++)
+ {
+ if (*line_ptr == '#')
+ {
+ *line_ptr = '\0';
+ break;
+ }
+ }
+
+ // cut trailing whitespaces from input line
+ for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
+ if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
+ *line_ptr = '\0';
+
+ // ignore empty lines
+ if (*line == '\0')
+ return FALSE;
+
+ // cut leading whitespaces from token
+ for (token = line; *token; token++)
+ if (*token != ' ' && *token != '\t')
+ break;
+
+ // start with empty value as reliable default
+ value = "";
+
+ token_value_separator_found = FALSE;
+
+ // find end of token to determine start of value
+ for (line_ptr = token; *line_ptr; line_ptr++)
+ {
+ // first look for an explicit token/value separator, like ':' or '='
+ if (*line_ptr == ':' || *line_ptr == '=')
+ {
+ *line_ptr = '\0'; // terminate token string
+ value = line_ptr + 1; // set beginning of value
+
+ token_value_separator_found = TRUE;
+
+ break;
+ }
+ }
+
+#if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
+ // fallback: if no token/value separator found, also allow whitespaces
+ if (!token_value_separator_found && !separator_required)
+ {
+ for (line_ptr = token; *line_ptr; line_ptr++)
+ {
+ if (*line_ptr == ' ' || *line_ptr == '\t')
+ {
+ *line_ptr = '\0'; // terminate token string
+ value = line_ptr + 1; // set beginning of value
+
+ token_value_separator_found = TRUE;
+
+ break;
+ }
+ }
+
+#if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
+ if (token_value_separator_found)
+ {
+ if (!token_value_separator_warning)
+ {
+ Debug("setup", "---");
+
+ if (filename != NULL)
+ {
+ Debug("setup", "missing token/value separator(s) in config file:");
+ Debug("setup", "- config file: '%s'", filename);
+ }
+ else
+ {
+ Debug("setup", "missing token/value separator(s):");
+ }
+
+ token_value_separator_warning = TRUE;
+ }
+
+ if (filename != NULL)
+ Debug("setup", "- line %d: '%s'", line_nr, line_raw);
+ else
+ Debug("setup", "- line: '%s'", line_raw);
+ }
+#endif
+ }
#endif
-static void *loadSetupFileData(char *filename, boolean use_hash)
+ // cut trailing whitespaces from token
+ for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
+ if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
+ *line_ptr = '\0';
+
+ // cut leading whitespaces from value
+ for (; *value; value++)
+ if (*value != ' ' && *value != '\t')
+ break;
+
+ *token_ptr = token;
+ *value_ptr = value;
+
+ return TRUE;
+}
+
+boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
+{
+ // while the internal (old) interface does not require a token/value
+ // separator (for downwards compatibility with existing files which
+ // don't use them), it is mandatory for the external (new) interface
+
+ return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
+}
+
+static boolean loadSetupFileData(void *setup_file_data, char *filename,
+ boolean top_recursion_level, boolean is_hash)
{
- char line[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
+ static SetupFileHash *include_filename_hash = NULL;
+ char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
char *token, *value, *line_ptr;
- void *setup_file_data, *insert_ptr = NULL;
+ void *insert_ptr = NULL;
boolean read_continued_line = FALSE;
- FILE *file;
+ File *file;
+ int line_nr = 0, token_count = 0, include_count = 0;
+
+#if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
+ token_value_separator_warning = FALSE;
+#endif
+
+#if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
+ token_already_exists_warning = FALSE;
+#endif
- if (!(file = fopen(filename, MODE_READ)))
+ if (!(file = openFile(filename, MODE_READ)))
{
- Error(ERR_WARN, "cannot open configuration file '%s'", filename);
+#if DEBUG_NO_CONFIG_FILE
+ Debug("setup", "cannot open configuration file '%s'", filename);
+#endif
- return NULL;
+ return FALSE;
}
- if (use_hash)
- setup_file_data = newSetupFileHash();
- else
- insert_ptr = setup_file_data = newSetupFileList("", "");
+ // use "insert pointer" to store list end for constant insertion complexity
+ if (!is_hash)
+ insert_ptr = setup_file_data;
- while (!feof(file))
+ // on top invocation, create hash to mark included files (to prevent loops)
+ if (top_recursion_level)
+ include_filename_hash = newSetupFileHash();
+
+ // mark this file as already included (to prevent including it again)
+ setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
+
+ while (!checkEndOfFile(file))
{
- /* read next line of input file */
- if (!fgets(line, MAX_LINE_LEN, file))
+ // read next line of input file
+ if (!getStringFromFile(file, line, MAX_LINE_LEN))
break;
- /* cut trailing newline or carriage return */
+ // check if line was completely read and is terminated by line break
+ if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
+ line_nr++;
+
+ // cut trailing line break (this can be newline and/or carriage return)
for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
*line_ptr = '\0';
+ // copy raw input line for later use (mainly debugging output)
+ strcpy(line_raw, line);
+
if (read_continued_line)
{
- /* cut leading whitespaces from input line */
- for (line_ptr = line; *line_ptr; line_ptr++)
- if (*line_ptr != ' ' && *line_ptr != '\t')
- break;
-
- /* append new line to existing line, if there is enough space */
+ // append new line to existing line, if there is enough space
if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
strcat(previous_line, line_ptr);
- strcpy(line, previous_line); /* copy storage buffer to line */
+ strcpy(line, previous_line); // copy storage buffer to line
read_continued_line = FALSE;
}
- /* if the last character is '\', continue at next line */
+ // if the last character is '\', continue at next line
if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
{
- line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
- strcpy(previous_line, line); /* copy line to storage buffer */
+ line[strlen(line) - 1] = '\0'; // cut off trailing backslash
+ strcpy(previous_line, line); // copy line to storage buffer
read_continued_line = TRUE;
continue;
}
- /* cut trailing comment from input line */
- for (line_ptr = line; *line_ptr; line_ptr++)
- {
- if (*line_ptr == '#')
- {
- *line_ptr = '\0';
- break;
- }
- }
-
- /* cut trailing whitespaces from input line */
- for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
- if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
- *line_ptr = '\0';
-
- /* ignore empty lines */
- if (*line == '\0')
+ if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
+ line_raw, line_nr, FALSE))
continue;
- /* cut leading whitespaces from token */
- for (token = line; *token; token++)
- if (*token != ' ' && *token != '\t')
- break;
-
- /* start with empty value as reliable default */
- value = "";
-
- /* find end of token to determine start of value */
- for (line_ptr = token; *line_ptr; line_ptr++)
+ if (*token)
{
-#if 1
- if (*line_ptr == ':' || *line_ptr == '=')
-#else
- if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
-#endif
+ if (strEqual(token, "include"))
{
- *line_ptr = '\0'; /* terminate token string */
- value = line_ptr + 1; /* set beginning of value */
-
- break;
- }
- }
+ if (getHashEntry(include_filename_hash, value) == NULL)
+ {
+ char *basepath = getBasePath(filename);
+ char *basename = getBaseName(value);
+ char *filename_include = getPath2(basepath, basename);
- /* cut trailing whitespaces from token */
- for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
- if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
- *line_ptr = '\0';
+ loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
- /* cut leading whitespaces from value */
- for (; *value; value++)
- if (*value != ' ' && *value != '\t')
- break;
+ free(basepath);
+ free(basename);
+ free(filename_include);
-#if 0
- if (*value == '\0')
- value = "true"; /* treat tokens without value as "true" */
+ include_count++;
+ }
+ else
+ {
+ Warn("ignoring already processed file '%s'", value);
+ }
+ }
+ else
+ {
+ if (is_hash)
+ {
+#if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
+ char *old_value =
+ getHashEntry((SetupFileHash *)setup_file_data, token);
+
+ if (old_value != NULL)
+ {
+ if (!token_already_exists_warning)
+ {
+ Debug("setup", "---");
+ Debug("setup", "duplicate token(s) found in config file:");
+ Debug("setup", "- config file: '%s'", filename);
+
+ token_already_exists_warning = TRUE;
+ }
+
+ Debug("setup", "- token: '%s' (in line %d)", token, line_nr);
+ Debug("setup", " old value: '%s'", old_value);
+ Debug("setup", " new value: '%s'", value);
+ }
#endif
- if (*token)
- {
- if (use_hash)
- setHashEntry((SetupFileHash *)setup_file_data, token, value);
- else
- insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
+ setHashEntry((SetupFileHash *)setup_file_data, token, value);
+ }
+ else
+ {
+ insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
+ }
+
+ token_count++;
+ }
}
}
- fclose(file);
+ closeFile(file);
- if (use_hash)
- {
- if (hashtable_count((SetupFileHash *)setup_file_data) == 0)
- Error(ERR_WARN, "configuration file '%s' is empty", filename);
- }
- else
- {
- SetupFileList *setup_file_list = (SetupFileList *)setup_file_data;
- SetupFileList *first_valid_list_entry = setup_file_list->next;
+#if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
+ if (token_value_separator_warning)
+ Debug("setup", "---");
+#endif
- /* free empty list header */
- setup_file_list->next = NULL;
- freeSetupFileList(setup_file_list);
- setup_file_data = first_valid_list_entry;
+#if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
+ if (token_already_exists_warning)
+ Debug("setup", "---");
+#endif
- if (first_valid_list_entry == NULL)
- Error(ERR_WARN, "configuration file '%s' is empty", filename);
- }
+ if (token_count == 0 && include_count == 0)
+ Warn("configuration file '%s' is empty", filename);
+
+ if (top_recursion_level)
+ freeSetupFileHash(include_filename_hash);
- return setup_file_data;
+ return TRUE;
}
-void saveSetupFileHash(SetupFileHash *hash, char *filename)
+static void saveSetupFileHash(SetupFileHash *hash, char *filename)
{
FILE *file;
if (!(file = fopen(filename, MODE_WRITE)))
{
- Error(ERR_WARN, "cannot write configuration file '%s'", filename);
+ Warn("cannot write configuration file '%s'", filename);
return;
}
SetupFileList *loadSetupFileList(char *filename)
{
- return (SetupFileList *)loadSetupFileData(filename, FALSE);
+ SetupFileList *setup_file_list = newSetupFileList("", "");
+ SetupFileList *first_valid_list_entry;
+
+ if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
+ {
+ freeSetupFileList(setup_file_list);
+
+ return NULL;
+ }
+
+ first_valid_list_entry = setup_file_list->next;
+
+ // free empty list header
+ setup_file_list->next = NULL;
+ freeSetupFileList(setup_file_list);
+
+ return first_valid_list_entry;
}
SetupFileHash *loadSetupFileHash(char *filename)
{
- return (SetupFileHash *)loadSetupFileData(filename, TRUE);
-}
+ SetupFileHash *setup_file_hash = newSetupFileHash();
-void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
- char *filename, char *identifier)
-{
- char *value = getHashEntry(setup_file_hash, TOKEN_STR_FILE_IDENTIFIER);
+ if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
+ {
+ freeSetupFileHash(setup_file_hash);
- if (value == NULL)
- Error(ERR_WARN, "config file '%s' has no file identifier", filename);
- else if (!checkCookieString(value, identifier))
- Error(ERR_WARN, "config file '%s' has wrong file identifier", filename);
+ return NULL;
+ }
+
+ return setup_file_hash;
}
-/* ========================================================================= */
-/* setup file stuff */
-/* ========================================================================= */
+// ============================================================================
+// setup file stuff
+// ============================================================================
#define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
#define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
#define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
+#define TOKEN_STR_LAST_USER "last_user"
-/* level directory info */
+// level directory info
#define LEVELINFO_TOKEN_IDENTIFIER 0
#define LEVELINFO_TOKEN_NAME 1
#define LEVELINFO_TOKEN_NAME_SORTING 2
#define LEVELINFO_TOKEN_AUTHOR 3
-#define LEVELINFO_TOKEN_IMPORTED_FROM 4
-#define LEVELINFO_TOKEN_IMPORTED_BY 5
-#define LEVELINFO_TOKEN_LEVELS 6
-#define LEVELINFO_TOKEN_FIRST_LEVEL 7
-#define LEVELINFO_TOKEN_SORT_PRIORITY 8
-#define LEVELINFO_TOKEN_LATEST_ENGINE 9
-#define LEVELINFO_TOKEN_LEVEL_GROUP 10
-#define LEVELINFO_TOKEN_READONLY 11
-#define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 12
-#define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 13
-#define LEVELINFO_TOKEN_GRAPHICS_SET 14
-#define LEVELINFO_TOKEN_SOUNDS_SET 15
-#define LEVELINFO_TOKEN_MUSIC_SET 16
-#define LEVELINFO_TOKEN_FILENAME 17
-#define LEVELINFO_TOKEN_FILETYPE 18
-#define LEVELINFO_TOKEN_HANDICAP 19
-#define LEVELINFO_TOKEN_SKIP_LEVELS 20
-
-#define NUM_LEVELINFO_TOKENS 21
+#define LEVELINFO_TOKEN_YEAR 4
+#define LEVELINFO_TOKEN_PROGRAM_TITLE 5
+#define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
+#define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
+#define LEVELINFO_TOKEN_IMPORTED_FROM 8
+#define LEVELINFO_TOKEN_IMPORTED_BY 9
+#define LEVELINFO_TOKEN_TESTED_BY 10
+#define LEVELINFO_TOKEN_LEVELS 11
+#define LEVELINFO_TOKEN_FIRST_LEVEL 12
+#define LEVELINFO_TOKEN_SORT_PRIORITY 13
+#define LEVELINFO_TOKEN_LATEST_ENGINE 14
+#define LEVELINFO_TOKEN_LEVEL_GROUP 15
+#define LEVELINFO_TOKEN_READONLY 16
+#define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
+#define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
+#define LEVELINFO_TOKEN_GRAPHICS_SET 19
+#define LEVELINFO_TOKEN_SOUNDS_SET_DEFAULT 20
+#define LEVELINFO_TOKEN_SOUNDS_SET_LOWPASS 21
+#define LEVELINFO_TOKEN_SOUNDS_SET 22
+#define LEVELINFO_TOKEN_MUSIC_SET 23
+#define LEVELINFO_TOKEN_FILENAME 24
+#define LEVELINFO_TOKEN_FILETYPE 25
+#define LEVELINFO_TOKEN_SPECIAL_FLAGS 26
+#define LEVELINFO_TOKEN_HANDICAP 27
+#define LEVELINFO_TOKEN_SKIP_LEVELS 28
+#define LEVELINFO_TOKEN_USE_EMC_TILES 29
+
+#define NUM_LEVELINFO_TOKENS 30
static LevelDirTree ldi;
static struct TokenInfo levelinfo_tokens[] =
{
- /* level directory info */
+ // level directory info
{ TYPE_STRING, &ldi.identifier, "identifier" },
{ TYPE_STRING, &ldi.name, "name" },
{ TYPE_STRING, &ldi.name_sorting, "name_sorting" },
{ TYPE_STRING, &ldi.author, "author" },
+ { TYPE_STRING, &ldi.year, "year" },
+ { TYPE_STRING, &ldi.program_title, "program_title" },
+ { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
+ { TYPE_STRING, &ldi.program_company, "program_company" },
{ TYPE_STRING, &ldi.imported_from, "imported_from" },
{ TYPE_STRING, &ldi.imported_by, "imported_by" },
+ { TYPE_STRING, &ldi.tested_by, "tested_by" },
{ TYPE_INTEGER, &ldi.levels, "levels" },
{ TYPE_INTEGER, &ldi.first_level, "first_level" },
{ TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
{ TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
{ TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
{ TYPE_STRING, &ldi.graphics_set, "graphics_set" },
+ { TYPE_STRING, &ldi.sounds_set_default,"sounds_set.default" },
+ { TYPE_STRING, &ldi.sounds_set_lowpass,"sounds_set.lowpass" },
{ TYPE_STRING, &ldi.sounds_set, "sounds_set" },
{ TYPE_STRING, &ldi.music_set, "music_set" },
{ TYPE_STRING, &ldi.level_filename, "filename" },
{ TYPE_STRING, &ldi.level_filetype, "filetype" },
+ { TYPE_STRING, &ldi.special_flags, "special_flags" },
{ TYPE_BOOLEAN, &ldi.handicap, "handicap" },
- { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" }
+ { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" },
+ { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" }
};
static struct TokenInfo artworkinfo_tokens[] =
{
- /* artwork directory info */
+ // artwork directory info
{ TYPE_STRING, &ldi.identifier, "identifier" },
{ TYPE_STRING, &ldi.subdir, "subdir" },
{ TYPE_STRING, &ldi.name, "name" },
{ TYPE_STRING, &ldi.name_sorting, "name_sorting" },
{ TYPE_STRING, &ldi.author, "author" },
+ { TYPE_STRING, &ldi.program_title, "program_title" },
+ { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
+ { TYPE_STRING, &ldi.program_company, "program_company" },
{ TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
{ TYPE_STRING, &ldi.basepath, "basepath" },
{ TYPE_STRING, &ldi.fullpath, "fullpath" },
{ -1, NULL, NULL },
};
+static char *optional_tokens[] =
+{
+ "program_title",
+ "program_copyright",
+ "program_company",
+
+ NULL
+};
+
static void setTreeInfoToDefaults(TreeInfo *ti, int type)
{
ti->type = type;
ti->name = getStringCopy(ANONYMOUS_NAME);
ti->name_sorting = NULL;
ti->author = getStringCopy(ANONYMOUS_NAME);
+ ti->year = NULL;
- ti->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
- ti->latest_engine = FALSE; /* default: get from level */
+ ti->program_title = NULL;
+ ti->program_copyright = NULL;
+ ti->program_company = NULL;
+
+ ti->sort_priority = LEVELCLASS_UNDEFINED; // default: least priority
+ ti->latest_engine = FALSE; // default: get from level
ti->parent_link = FALSE;
ti->in_user_dir = FALSE;
ti->user_defined = FALSE;
{
ti->imported_from = NULL;
ti->imported_by = NULL;
+ ti->tested_by = NULL;
ti->graphics_set_ecs = NULL;
ti->graphics_set_aga = NULL;
ti->graphics_set = NULL;
+ ti->sounds_set_default = NULL;
+ ti->sounds_set_lowpass = NULL;
ti->sounds_set = NULL;
ti->music_set = NULL;
ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
ti->level_filename = NULL;
ti->level_filetype = NULL;
+ ti->special_flags = NULL;
+
ti->levels = 0;
ti->first_level = 0;
ti->last_level = 0;
ti->readonly = TRUE;
ti->handicap = TRUE;
ti->skip_levels = FALSE;
+
+ ti->use_emc_tiles = FALSE;
}
}
{
if (parent == NULL)
{
- Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
+ Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
return;
}
- /* copy all values from the parent structure */
+ // copy all values from the parent structure
ti->type = parent->type;
ti->name = getStringCopy(ANONYMOUS_NAME);
ti->name_sorting = NULL;
ti->author = getStringCopy(parent->author);
+ ti->year = getStringCopy(parent->year);
+
+ ti->program_title = getStringCopy(parent->program_title);
+ ti->program_copyright = getStringCopy(parent->program_copyright);
+ ti->program_company = getStringCopy(parent->program_company);
ti->sort_priority = parent->sort_priority;
ti->latest_engine = parent->latest_engine;
{
ti->imported_from = getStringCopy(parent->imported_from);
ti->imported_by = getStringCopy(parent->imported_by);
-
- ti->graphics_set_ecs = NULL;
- ti->graphics_set_aga = NULL;
- ti->graphics_set = NULL;
- ti->sounds_set = NULL;
- ti->music_set = NULL;
+ ti->tested_by = getStringCopy(parent->tested_by);
+
+ ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
+ ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
+ ti->graphics_set = getStringCopy(parent->graphics_set);
+ ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
+ ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
+ ti->sounds_set = getStringCopy(parent->sounds_set);
+ ti->music_set = getStringCopy(parent->music_set);
ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
ti->music_path = getStringCopy(UNDEFINED_FILENAME);
- ti->level_filename = NULL;
- ti->level_filetype = NULL;
+ ti->level_filename = getStringCopy(parent->level_filename);
+ ti->level_filetype = getStringCopy(parent->level_filetype);
- ti->levels = 0;
- ti->first_level = 0;
- ti->last_level = 0;
+ ti->special_flags = getStringCopy(parent->special_flags);
+
+ ti->levels = parent->levels;
+ ti->first_level = parent->first_level;
+ ti->last_level = parent->last_level;
ti->level_group = FALSE;
- ti->handicap_level = 0;
- ti->readonly = TRUE;
- ti->handicap = TRUE;
- ti->skip_levels = FALSE;
+ ti->handicap_level = parent->handicap_level;
+ ti->readonly = parent->readonly;
+ ti->handicap = parent->handicap;
+ ti->skip_levels = parent->skip_levels;
+
+ ti->use_emc_tiles = parent->use_emc_tiles;
}
}
-static void freeTreeInfo(TreeInfo *ti)
+static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
+{
+ TreeInfo *ti_copy = newTreeInfo();
+
+ // copy all values from the original structure
+
+ ti_copy->type = ti->type;
+
+ ti_copy->node_top = ti->node_top;
+ ti_copy->node_parent = ti->node_parent;
+ ti_copy->node_group = ti->node_group;
+ ti_copy->next = ti->next;
+
+ ti_copy->cl_first = ti->cl_first;
+ ti_copy->cl_cursor = ti->cl_cursor;
+
+ ti_copy->subdir = getStringCopy(ti->subdir);
+ ti_copy->fullpath = getStringCopy(ti->fullpath);
+ ti_copy->basepath = getStringCopy(ti->basepath);
+ ti_copy->identifier = getStringCopy(ti->identifier);
+ ti_copy->name = getStringCopy(ti->name);
+ ti_copy->name_sorting = getStringCopy(ti->name_sorting);
+ ti_copy->author = getStringCopy(ti->author);
+ ti_copy->year = getStringCopy(ti->year);
+
+ ti_copy->program_title = getStringCopy(ti->program_title);
+ ti_copy->program_copyright = getStringCopy(ti->program_copyright);
+ ti_copy->program_company = getStringCopy(ti->program_company);
+
+ ti_copy->imported_from = getStringCopy(ti->imported_from);
+ ti_copy->imported_by = getStringCopy(ti->imported_by);
+ ti_copy->tested_by = getStringCopy(ti->tested_by);
+
+ ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
+ ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
+ ti_copy->graphics_set = getStringCopy(ti->graphics_set);
+ ti_copy->sounds_set_default = getStringCopy(ti->sounds_set_default);
+ ti_copy->sounds_set_lowpass = getStringCopy(ti->sounds_set_lowpass);
+ ti_copy->sounds_set = getStringCopy(ti->sounds_set);
+ ti_copy->music_set = getStringCopy(ti->music_set);
+ ti_copy->graphics_path = getStringCopy(ti->graphics_path);
+ ti_copy->sounds_path = getStringCopy(ti->sounds_path);
+ ti_copy->music_path = getStringCopy(ti->music_path);
+
+ ti_copy->level_filename = getStringCopy(ti->level_filename);
+ ti_copy->level_filetype = getStringCopy(ti->level_filetype);
+
+ ti_copy->special_flags = getStringCopy(ti->special_flags);
+
+ ti_copy->levels = ti->levels;
+ ti_copy->first_level = ti->first_level;
+ ti_copy->last_level = ti->last_level;
+ ti_copy->sort_priority = ti->sort_priority;
+
+ ti_copy->latest_engine = ti->latest_engine;
+
+ ti_copy->level_group = ti->level_group;
+ ti_copy->parent_link = ti->parent_link;
+ ti_copy->in_user_dir = ti->in_user_dir;
+ ti_copy->user_defined = ti->user_defined;
+ ti_copy->readonly = ti->readonly;
+ ti_copy->handicap = ti->handicap;
+ ti_copy->skip_levels = ti->skip_levels;
+
+ ti_copy->use_emc_tiles = ti->use_emc_tiles;
+
+ ti_copy->color = ti->color;
+ ti_copy->class_desc = getStringCopy(ti->class_desc);
+ ti_copy->handicap_level = ti->handicap_level;
+
+ ti_copy->infotext = getStringCopy(ti->infotext);
+
+ return ti_copy;
+}
+
+void freeTreeInfo(TreeInfo *ti)
{
if (ti == NULL)
return;
checked_free(ti->name);
checked_free(ti->name_sorting);
checked_free(ti->author);
+ checked_free(ti->year);
+
+ checked_free(ti->program_title);
+ checked_free(ti->program_copyright);
+ checked_free(ti->program_company);
checked_free(ti->class_desc);
{
checked_free(ti->imported_from);
checked_free(ti->imported_by);
+ checked_free(ti->tested_by);
checked_free(ti->graphics_set_ecs);
checked_free(ti->graphics_set_aga);
checked_free(ti->graphics_set);
+ checked_free(ti->sounds_set_default);
+ checked_free(ti->sounds_set_lowpass);
checked_free(ti->sounds_set);
checked_free(ti->music_set);
checked_free(ti->level_filename);
checked_free(ti->level_filetype);
+
+ checked_free(ti->special_flags);
}
+ // recursively free child node
+ if (ti->node_group)
+ freeTreeInfo(ti->node_group);
+
+ // recursively free next node
+ if (ti->next)
+ freeTreeInfo(ti->next);
+
checked_free(ti);
}
if (token_value == NULL)
return;
- /* set setup field to corresponding token value */
+ // set setup field to corresponding token value
switch (token_type)
{
case TYPE_BOOLEAN:
*(boolean *)setup_value = get_boolean_from_string(token_value);
break;
+ case TYPE_SWITCH3:
+ *(int *)setup_value = get_switch3_from_string(token_value);
+ break;
+
case TYPE_KEY:
*(Key *)setup_value = getKeyFromKeyName(token_value);
break;
*(char **)setup_value = getStringCopy(token_value);
break;
+ case TYPE_PLAYER:
+ *(int *)setup_value = get_player_nr_from_string(token_value);
+ break;
+
default:
break;
}
{
const TreeInfo *entry1 = *((TreeInfo **)object1);
const TreeInfo *entry2 = *((TreeInfo **)object2);
- int class_sorting1, class_sorting2;
+ int class_sorting1 = 0, class_sorting2 = 0;
int compare_result;
if (entry1->type == TREE_TYPE_LEVEL_DIR)
class_sorting1 = LEVELSORTING(entry1);
class_sorting2 = LEVELSORTING(entry2);
}
- else
+ else if (entry1->type == TREE_TYPE_GRAPHICS_DIR ||
+ entry1->type == TREE_TYPE_SOUNDS_DIR ||
+ entry1->type == TREE_TYPE_MUSIC_DIR)
{
class_sorting1 = ARTWORKSORTING(entry1);
class_sorting2 = ARTWORKSORTING(entry2);
return compare_result;
}
-static void createParentTreeInfoNode(TreeInfo *node_parent)
+static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
{
TreeInfo *ti_new;
if (node_parent == NULL)
- return;
+ return NULL;
ti_new = newTreeInfo();
setTreeInfoToDefaults(ti_new, node_parent->type);
setString(&ti_new->name, ".. (parent directory)");
setString(&ti_new->name_sorting, ti_new->name);
- setString(&ti_new->subdir, "..");
+ setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
setString(&ti_new->fullpath, node_parent->fullpath);
ti_new->sort_priority = node_parent->sort_priority;
setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
pushTreeInfo(&node_parent->node_group, ti_new);
+
+ return ti_new;
+}
+
+static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
+{
+ TreeInfo *ti_new, *ti_new2;
+
+ if (node_first == NULL)
+ return NULL;
+
+ ti_new = newTreeInfo();
+ setTreeInfoToDefaults(ti_new, TREE_TYPE_LEVEL_DIR);
+
+ ti_new->node_parent = NULL;
+ ti_new->parent_link = FALSE;
+
+ setString(&ti_new->identifier, node_first->identifier);
+ setString(&ti_new->name, "level sets");
+ setString(&ti_new->name_sorting, ti_new->name);
+
+ setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
+ setString(&ti_new->fullpath, ".");
+
+ ti_new->sort_priority = node_first->sort_priority;;
+ ti_new->latest_engine = node_first->latest_engine;
+
+ setString(&ti_new->class_desc, "level sets");
+
+ ti_new->node_group = node_first;
+ ti_new->level_group = TRUE;
+
+ ti_new2 = createParentTreeInfoNode(ti_new);
+
+ setString(&ti_new2->name, ".. (main menu)");
+ setString(&ti_new2->name_sorting, ti_new2->name);
+
+ return ti_new;
}
-/* -------------------------------------------------------------------------- */
-/* functions for handling level and custom artwork info cache */
-/* -------------------------------------------------------------------------- */
+// ----------------------------------------------------------------------------
+// functions for handling level and custom artwork info cache
+// ----------------------------------------------------------------------------
-static void LoadArtworkInfoCache()
+static void LoadArtworkInfoCache(void)
{
InitCacheDirectory();
{
char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
- /* try to load artwork info hash from already existing cache file */
+ // try to load artwork info hash from already existing cache file
artworkinfo_cache_old = loadSetupFileHash(filename);
- /* if no artwork info cache file was found, start with empty hash */
+ // if no artwork info cache file was found, start with empty hash
if (artworkinfo_cache_old == NULL)
artworkinfo_cache_old = newSetupFileHash();
artworkinfo_cache_new = newSetupFileHash();
}
-static void SaveArtworkInfoCache()
+static void SaveArtworkInfoCache(void)
{
char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
return prefix;
}
-/* (identical to above function, but separate string buffer needed -- nasty) */
+// (identical to above function, but separate string buffer needed -- nasty)
static char *getCacheToken(char *prefix, char *suffix)
{
static char *token = NULL;
return token;
}
-static char *getFileTimestamp(char *filename)
+static char *getFileTimestampString(char *filename)
{
- struct stat file_status;
-
- if (stat(filename, &file_status) != 0) /* cannot stat file */
- return getStringCopy(i_to_a(0));
-
- return getStringCopy(i_to_a(file_status.st_mtime));
+ return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
}
static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
if (timestamp_string == NULL)
return TRUE;
- if (stat(filename, &file_status) != 0) /* cannot stat file */
+ if (stat(filename, &file_status) != 0) // cannot stat file
return TRUE;
return (file_status.st_mtime != atoi(timestamp_string));
if (!use_artworkinfo_cache)
return NULL;
+ if (optional_tokens_hash == NULL)
+ {
+ int i;
+
+ // create hash from list of optional tokens (for quick access)
+ optional_tokens_hash = newSetupFileHash();
+ for (i = 0; optional_tokens[i] != NULL; i++)
+ setHashEntry(optional_tokens_hash, optional_tokens[i], "");
+ }
+
if (cached)
{
int i;
artwork_info = newTreeInfo();
setTreeInfoToDefaults(artwork_info, type);
- /* set all structure fields according to the token/value pairs */
+ // set all structure fields according to the token/value pairs
ldi = *artwork_info;
for (i = 0; artworkinfo_tokens[i].type != -1; i++)
{
- char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
+ char *token_suffix = artworkinfo_tokens[i].text;
+ char *token = getCacheToken(token_prefix, token_suffix);
char *value = getHashEntry(artworkinfo_cache_old, token);
+ boolean optional =
+ (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
setSetupInfo(artworkinfo_tokens, i, value);
- /* check if cache entry for this item is invalid or incomplete */
- if (value == NULL)
+ // check if cache entry for this item is mandatory, but missing
+ if (value == NULL && !optional)
{
-#if 1
- printf("::: - WARNING: cache entry '%s' invalid\n", token);
-#endif
+ Warn("missing cache entry '%s'", token);
cached = FALSE;
}
}
+
*artwork_info = ldi;
}
char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
ARTWORKINFO_FILENAME(type));
- /* check if corresponding "levelinfo.conf" file has changed */
+ // check if corresponding "levelinfo.conf" file has changed
token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
cached = FALSE;
- /* check if corresponding "<artworkinfo>.conf" file has changed */
+ // check if corresponding "<artworkinfo>.conf" file has changed
token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
cached = FALSE;
-#if 0
- if (!cached)
- printf("::: '%s': INVALIDATED FROM CACHE BY TIMESTAMP\n", identifier);
-#endif
-
checked_free(filename_levelinfo);
checked_free(filename_artworkinfo);
}
LEVELINFO_FILENAME);
char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
ARTWORKINFO_FILENAME(type));
- char *timestamp_levelinfo = getFileTimestamp(filename_levelinfo);
- char *timestamp_artworkinfo = getFileTimestamp(filename_artworkinfo);
+ char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
+ char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
}
-/* -------------------------------------------------------------------------- */
-/* functions for loading level info and custom artwork info */
-/* -------------------------------------------------------------------------- */
+// ----------------------------------------------------------------------------
+// functions for loading level info and custom artwork info
+// ----------------------------------------------------------------------------
+
+int GetZipFileTreeType(char *zip_filename)
+{
+ static char *top_dir_path = NULL;
+ static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
+ static char *conf_basename[NUM_BASE_TREE_TYPES] =
+ {
+ GRAPHICSINFO_FILENAME,
+ SOUNDSINFO_FILENAME,
+ MUSICINFO_FILENAME,
+ LEVELINFO_FILENAME
+ };
+ int j;
+
+ checked_free(top_dir_path);
+ top_dir_path = NULL;
+
+ for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
+ {
+ checked_free(top_dir_conf_filename[j]);
+ top_dir_conf_filename[j] = NULL;
+ }
+
+ char **zip_entries = zip_list(zip_filename);
+
+ // check if zip file successfully opened
+ if (zip_entries == NULL || zip_entries[0] == NULL)
+ return TREE_TYPE_UNDEFINED;
+
+ // first zip file entry is expected to be top level directory
+ char *top_dir = zip_entries[0];
+
+ // check if valid top level directory found in zip file
+ if (!strSuffix(top_dir, "/"))
+ return TREE_TYPE_UNDEFINED;
+
+ // get filenames of valid configuration files in top level directory
+ for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
+ top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
+
+ int tree_type = TREE_TYPE_UNDEFINED;
+ int e = 0;
+
+ while (zip_entries[e] != NULL)
+ {
+ // check if every zip file entry is below top level directory
+ if (!strPrefix(zip_entries[e], top_dir))
+ return TREE_TYPE_UNDEFINED;
+
+ // check if this zip file entry is a valid configuration filename
+ for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
+ {
+ if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
+ {
+ // only exactly one valid configuration file allowed
+ if (tree_type != TREE_TYPE_UNDEFINED)
+ return TREE_TYPE_UNDEFINED;
+
+ tree_type = j;
+ }
+ }
+
+ e++;
+ }
+
+ return tree_type;
+}
+
+static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
+ int tree_type)
+{
+ static char *top_dir_path = NULL;
+ static char *top_dir_conf_filename = NULL;
+
+ checked_free(top_dir_path);
+ checked_free(top_dir_conf_filename);
+
+ top_dir_path = NULL;
+ top_dir_conf_filename = NULL;
+
+ char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
+ ARTWORKINFO_FILENAME(tree_type));
+
+ // check if valid configuration filename determined
+ if (conf_basename == NULL || strEqual(conf_basename, ""))
+ return FALSE;
+
+ char **zip_entries = zip_list(zip_filename);
+
+ // check if zip file successfully opened
+ if (zip_entries == NULL || zip_entries[0] == NULL)
+ return FALSE;
+
+ // first zip file entry is expected to be top level directory
+ char *top_dir = zip_entries[0];
+
+ // check if valid top level directory found in zip file
+ if (!strSuffix(top_dir, "/"))
+ return FALSE;
+
+ // get path of extracted top level directory
+ top_dir_path = getPath2(directory, top_dir);
+
+ // remove trailing directory separator from top level directory path
+ // (required to be able to check for file and directory in next step)
+ top_dir_path[strlen(top_dir_path) - 1] = '\0';
+
+ // check if zip file's top level directory already exists in target directory
+ if (fileExists(top_dir_path)) // (checks for file and directory)
+ return FALSE;
+
+ // get filename of configuration file in top level directory
+ top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
-/* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
+ boolean found_top_dir_conf_filename = FALSE;
+ int i = 0;
+
+ while (zip_entries[i] != NULL)
+ {
+ // check if every zip file entry is below top level directory
+ if (!strPrefix(zip_entries[i], top_dir))
+ return FALSE;
+
+ // check if this zip file entry is the configuration filename
+ if (strEqual(zip_entries[i], top_dir_conf_filename))
+ found_top_dir_conf_filename = TRUE;
+
+ i++;
+ }
+
+ // check if valid configuration filename was found in zip file
+ if (!found_top_dir_conf_filename)
+ return FALSE;
+
+ return TRUE;
+}
+
+char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
+ int tree_type)
+{
+ boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
+ tree_type);
+
+ if (!zip_file_valid)
+ {
+ Warn("zip file '%s' rejected!", zip_filename);
+
+ return NULL;
+ }
+
+ char **zip_entries = zip_extract(zip_filename, directory);
+
+ if (zip_entries == NULL)
+ {
+ Warn("zip file '%s' could not be extracted!", zip_filename);
+
+ return NULL;
+ }
+
+ Info("zip file '%s' successfully extracted!", zip_filename);
+
+ // first zip file entry contains top level directory
+ char *top_dir = zip_entries[0];
+
+ // remove trailing directory separator from top level directory
+ top_dir[strlen(top_dir) - 1] = '\0';
+
+ return top_dir;
+}
+
+static void ProcessZipFilesInDirectory(char *directory, int tree_type)
+{
+ Directory *dir;
+ DirectoryEntry *dir_entry;
+
+ if ((dir = openDirectory(directory)) == NULL)
+ {
+ // display error if directory is main "options.graphics_directory" etc.
+ if (tree_type == TREE_TYPE_LEVEL_DIR ||
+ directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
+ Warn("cannot read directory '%s'", directory);
+
+ return;
+ }
+
+ while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
+ {
+ // skip non-zip files (and also directories with zip extension)
+ if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
+ continue;
+
+ char *zip_filename = getPath2(directory, dir_entry->basename);
+ char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
+ char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
+
+ // check if zip file hasn't already been extracted or rejected
+ if (!fileExists(zip_filename_extracted) &&
+ !fileExists(zip_filename_rejected))
+ {
+ char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
+ tree_type);
+ char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
+ zip_filename_rejected);
+ FILE *marker_file;
+
+ // create empty file to mark zip file as extracted or rejected
+ if ((marker_file = fopen(marker_filename, MODE_WRITE)))
+ fclose(marker_file);
+
+ free(zip_filename);
+ free(zip_filename_extracted);
+ free(zip_filename_rejected);
+ }
+ }
+
+ closeDirectory(dir);
+}
+
+// forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
LevelDirTree *leveldir_new = NULL;
int i;
- /* unless debugging, silently ignore directories without "levelinfo.conf" */
+ // unless debugging, silently ignore directories without "levelinfo.conf"
if (!options.debug && !fileExists(filename))
{
free(directory_path);
if (setup_file_hash == NULL)
{
- Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
+#if DEBUG_NO_CONFIG_FILE
+ Debug("setup", "ignoring level directory '%s'", directory_path);
+#endif
free(directory_path);
free(filename);
leveldir_new->subdir = getStringCopy(directory_name);
- checkSetupFileHashIdentifier(setup_file_hash, filename,
- getCookie("LEVELINFO"));
-
- /* set all structure fields according to the token/value pairs */
+ // set all structure fields according to the token/value pairs
ldi = *leveldir_new;
for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
setSetupInfo(levelinfo_tokens, i,
if (leveldir_new->name_sorting == NULL)
leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
- if (node_parent == NULL) /* top level group */
+ if (node_parent == NULL) // top level group
{
leveldir_new->basepath = getStringCopy(level_directory);
leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
}
- else /* sub level group */
+ else // sub level group
{
leveldir_new->basepath = getStringCopy(node_parent->basepath);
leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
}
-#if 0
- if (leveldir_new->levels < 1)
- leveldir_new->levels = 1;
-#endif
-
leveldir_new->last_level =
leveldir_new->first_level + leveldir_new->levels - 1;
leveldir_new->in_user_dir =
(!strEqual(leveldir_new->basepath, options.level_directory));
- /* adjust some settings if user's private level directory was detected */
+ // adjust some settings if user's private level directory was detected
if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
leveldir_new->in_user_dir &&
(strEqual(leveldir_new->subdir, getLoginName()) ||
setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
- leveldir_new->handicap_level = /* set handicap to default value */
+ leveldir_new->handicap_level = // set handicap to default value
(leveldir_new->user_defined || !leveldir_new->handicap ?
leveldir_new->last_level : leveldir_new->first_level);
- if (leveldir_new->level_group)
- DrawInitText(leveldir_new->name, 150, FC_YELLOW);
-
-#if 0
- /* !!! don't skip sets without levels (else artwork base sets are missing) */
-#if 1
- if (leveldir_new->levels < 1 && !leveldir_new->level_group)
- {
- /* skip level sets without levels (which are probably artwork base sets) */
-
- freeSetupFileHash(setup_file_hash);
- free(directory_path);
- free(filename);
-
- return FALSE;
- }
-#endif
-#endif
+ DrawInitText(leveldir_new->name, 150, FC_YELLOW);
pushTreeInfo(node_first, leveldir_new);
if (leveldir_new->level_group)
{
- /* create node to link back to current level directory */
+ // create node to link back to current level directory
createParentTreeInfoNode(leveldir_new);
- /* recursively step into sub-directory and look for more level series */
+ // recursively step into sub-directory and look for more level series
LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
leveldir_new, directory_path);
}
TreeInfo *node_parent,
char *level_directory)
{
- DIR *dir;
- struct dirent *dir_entry;
+ // ---------- 1st stage: process any level set zip files ----------
+
+ ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
+
+ // ---------- 2nd stage: check for level set directories ----------
+
+ Directory *dir;
+ DirectoryEntry *dir_entry;
boolean valid_entry_found = FALSE;
- if ((dir = opendir(level_directory)) == NULL)
+ if ((dir = openDirectory(level_directory)) == NULL)
{
- Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
+ Warn("cannot read level directory '%s'", level_directory);
+
return;
}
- while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
+ while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
{
- struct stat file_status;
- char *directory_name = dir_entry->d_name;
+ char *directory_name = dir_entry->basename;
char *directory_path = getPath2(level_directory, directory_name);
- /* skip entries for current and parent directory */
+ // skip entries for current and parent directory
if (strEqual(directory_name, ".") ||
strEqual(directory_name, ".."))
{
free(directory_path);
+
continue;
}
- /* find out if directory entry is itself a directory */
- if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
- (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
+ // find out if directory entry is itself a directory
+ if (!dir_entry->is_directory) // not a directory
{
free(directory_path);
+
continue;
}
directory_name);
}
- closedir(dir);
+ closeDirectory(dir);
- /* special case: top level directory may directly contain "levelinfo.conf" */
+ // special case: top level directory may directly contain "levelinfo.conf"
if (node_parent == NULL && !valid_entry_found)
{
- /* check if this directory directly contains a file "levelinfo.conf" */
+ // check if this directory directly contains a file "levelinfo.conf"
valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
level_directory, ".");
}
if (!valid_entry_found)
- Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
+ Warn("cannot find any valid level series in directory '%s'",
level_directory);
}
-boolean AdjustGraphicsForEMC()
+boolean AdjustGraphicsForEMC(void)
{
boolean settings_changed = FALSE;
return settings_changed;
}
-void LoadLevelInfo()
+boolean AdjustSoundsForEMC(void)
+{
+ boolean settings_changed = FALSE;
+
+ settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
+ settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
+
+ return settings_changed;
+}
+
+void LoadLevelInfo(void)
{
InitUserLevelDirectory(getLoginName());
LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
+ leveldir_first = createTopTreeInfoNode(leveldir_first);
+
/* after loading all level set information, clone the level directory tree
and remove all level sets without levels (these may still contain artwork
to be offered in the setup menu as "custom artwork", and are therefore
cloneTree(&leveldir_first, leveldir_first_all, TRUE);
AdjustGraphicsForEMC();
+ AdjustSoundsForEMC();
- /* before sorting, the first entries will be from the user directory */
+ // before sorting, the first entries will be from the user directory
leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
if (leveldir_first == NULL)
- Error(ERR_EXIT, "cannot find any valid level series in any directory");
+ Fail("cannot find any valid level series in any directory");
sortTreeInfo(&leveldir_first);
-#if 0
+#if ENABLE_UNUSED_CODE
dumpTreeInfo(leveldir_first, 0);
#endif
}
if (fileExists(filename))
setup_file_hash = loadSetupFileHash(filename);
- if (setup_file_hash == NULL) /* no config file -- look for artwork files */
+ if (setup_file_hash == NULL) // no config file -- look for artwork files
{
- DIR *dir;
- struct dirent *dir_entry;
+ Directory *dir;
+ DirectoryEntry *dir_entry;
boolean valid_file_found = FALSE;
- if ((dir = opendir(directory_path)) != NULL)
+ if ((dir = openDirectory(directory_path)) != NULL)
{
- while ((dir_entry = readdir(dir)) != NULL)
+ while ((dir_entry = readDirectory(dir)) != NULL)
{
- char *entry_name = dir_entry->d_name;
-
- if (FileIsArtworkType(entry_name, type))
+ if (FileIsArtworkType(dir_entry->filename, type))
{
valid_file_found = TRUE;
+
break;
}
}
- closedir(dir);
+ closeDirectory(dir);
}
if (!valid_file_found)
{
+#if DEBUG_NO_CONFIG_FILE
if (!strEqual(directory_name, "."))
- Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
+ Debug("setup", "ignoring artwork directory '%s'", directory_path);
+#endif
free(directory_path);
free(filename);
artwork_new->subdir = getStringCopy(directory_name);
- if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
+ if (setup_file_hash) // (before defining ".color" and ".class_desc")
{
-#if 0
- checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
-#endif
-
- /* set all structure fields according to the token/value pairs */
+ // set all structure fields according to the token/value pairs
ldi = *artwork_new;
for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
setSetupInfo(levelinfo_tokens, i,
artwork_new->name_sorting = getStringCopy(artwork_new->name);
}
- if (node_parent == NULL) /* top level group */
+ if (node_parent == NULL) // top level group
{
artwork_new->basepath = getStringCopy(base_directory);
artwork_new->fullpath = getStringCopy(artwork_new->subdir);
}
- else /* sub level group */
+ else // sub level group
{
artwork_new->basepath = getStringCopy(node_parent->basepath);
artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
artwork_new->in_user_dir =
(!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
- /* (may use ".sort_priority" from "setup_file_hash" above) */
+ // (may use ".sort_priority" from "setup_file_hash" above)
artwork_new->color = ARTWORKCOLOR(artwork_new);
setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
- if (setup_file_hash == NULL) /* (after determining ".user_defined") */
+ if (setup_file_hash == NULL) // (after determining ".user_defined")
{
if (strEqual(artwork_new->subdir, "."))
{
artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
}
- /* set to new values after changing ".sort_priority" */
+ // set to new values after changing ".sort_priority"
artwork_new->color = ARTWORKCOLOR(artwork_new);
setString(&artwork_new->class_desc,
setString(&artwork_new->name_sorting, artwork_new->name);
}
-#if 0
- DrawInitText(artwork_new->name, 150, FC_YELLOW);
-#endif
-
pushTreeInfo(node_first, artwork_new);
freeSetupFileHash(setup_file_hash);
TreeInfo *node_parent,
char *base_directory, int type)
{
- DIR *dir;
- struct dirent *dir_entry;
+ // ---------- 1st stage: process any artwork set zip files ----------
+
+ ProcessZipFilesInDirectory(base_directory, type);
+
+ // ---------- 2nd stage: check for artwork set directories ----------
+
+ Directory *dir;
+ DirectoryEntry *dir_entry;
boolean valid_entry_found = FALSE;
- if ((dir = opendir(base_directory)) == NULL)
+ if ((dir = openDirectory(base_directory)) == NULL)
{
- /* display error if directory is main "options.graphics_directory" etc. */
+ // display error if directory is main "options.graphics_directory" etc.
if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
- Error(ERR_WARN, "cannot read directory '%s'", base_directory);
+ Warn("cannot read directory '%s'", base_directory);
return;
}
- while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
+ while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
{
- struct stat file_status;
- char *directory_name = dir_entry->d_name;
+ char *directory_name = dir_entry->basename;
char *directory_path = getPath2(base_directory, directory_name);
- /* skip directory entries for current and parent directory */
+ // skip directory entries for current and parent directory
if (strEqual(directory_name, ".") ||
strEqual(directory_name, ".."))
{
free(directory_path);
+
continue;
}
- /* skip directory entries which are not a directory or are not accessible */
- if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
- (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
+ // skip directory entries which are not a directory
+ if (!dir_entry->is_directory) // not a directory
{
free(directory_path);
+
continue;
}
free(directory_path);
- /* check if this directory contains artwork with or without config file */
+ // check if this directory contains artwork with or without config file
valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
base_directory,
directory_name, type);
}
- closedir(dir);
+ closeDirectory(dir);
- /* check if this directory directly contains artwork itself */
+ // check if this directory directly contains artwork itself
valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
base_directory, ".",
type);
if (!valid_entry_found)
- Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
- base_directory);
+ Warn("cannot find any valid artwork in directory '%s'", base_directory);
}
static TreeInfo *getDummyArtworkInfo(int type)
{
- /* this is only needed when there is completely no artwork available */
+ // this is only needed when there is completely no artwork available
TreeInfo *artwork_new = newTreeInfo();
setTreeInfoToDefaults(artwork_new, type);
return artwork_new;
}
-void LoadArtworkInfo()
+void SetCurrentArtwork(int type)
+{
+ ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
+ ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
+ char *setup_set = SETUP_ARTWORK_SET(setup, type);
+ char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
+
+ // set current artwork to artwork configured in setup menu
+ *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
+
+ // if not found, set current artwork to default artwork
+ if (*current_ptr == NULL)
+ *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
+
+ // if not found, set current artwork to first artwork in tree
+ if (*current_ptr == NULL)
+ *current_ptr = getFirstValidTreeInfoEntry(first_node);
+}
+
+void ChangeCurrentArtworkIfNeeded(int type)
+{
+ char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
+ char *setup_set = SETUP_ARTWORK_SET(setup, type);
+
+ if (!strEqual(current_identifier, setup_set))
+ SetCurrentArtwork(type);
+}
+
+void LoadArtworkInfo(void)
{
LoadArtworkInfoCache();
if (artwork.mus_first == NULL)
artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
- /* before sorting, the first entries will be from the user directory */
- artwork.gfx_current =
- getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
- if (artwork.gfx_current == NULL)
- artwork.gfx_current =
- getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
- if (artwork.gfx_current == NULL)
- artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
-
- artwork.snd_current =
- getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
- if (artwork.snd_current == NULL)
- artwork.snd_current =
- getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
- if (artwork.snd_current == NULL)
- artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
-
- artwork.mus_current =
- getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
- if (artwork.mus_current == NULL)
- artwork.mus_current =
- getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
- if (artwork.mus_current == NULL)
- artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
+ // before sorting, the first entries will be from the user directory
+ SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
+ SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
+ SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
artwork.gfx_current_identifier = artwork.gfx_current->identifier;
artwork.snd_current_identifier = artwork.snd_current->identifier;
artwork.mus_current_identifier = artwork.mus_current->identifier;
-#if 0
- printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
- printf("sounds set == %s\n\n", artwork.snd_current_identifier);
- printf("music set == %s\n\n", artwork.mus_current_identifier);
+#if ENABLE_UNUSED_CODE
+ Debug("setup:LoadArtworkInfo", "graphics set == %s",
+ artwork.gfx_current_identifier);
+ Debug("setup:LoadArtworkInfo", "sounds set == %s",
+ artwork.snd_current_identifier);
+ Debug("setup:LoadArtworkInfo", "music set == %s",
+ artwork.mus_current_identifier);
#endif
sortTreeInfo(&artwork.gfx_first);
sortTreeInfo(&artwork.snd_first);
sortTreeInfo(&artwork.mus_first);
-#if 0
+#if ENABLE_UNUSED_CODE
dumpTreeInfo(artwork.gfx_first, 0);
dumpTreeInfo(artwork.snd_first, 0);
dumpTreeInfo(artwork.mus_first, 0);
#endif
}
-void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
- LevelDirTree *level_node)
+static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
+ LevelDirTree *level_node)
{
int type = (*artwork_node)->type;
- /* recursively check all level directories for artwork sub-directories */
+ // recursively check all level directories for artwork sub-directories
while (level_node)
{
- /* check all tree entries for artwork, but skip parent link entries */
+ // check all tree entries for artwork, but skip parent link entries
if (!level_node->parent_link)
{
TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
- if (topnode_last != *artwork_node) /* check for newly added node */
+ if (topnode_last != *artwork_node) // check for newly added node
{
artwork_new = *artwork_node;
free(path);
}
- /* insert artwork info (from old cache or filesystem) into new cache */
+ // insert artwork info (from old cache or filesystem) into new cache
if (artwork_new != NULL)
setArtworkInfoCacheEntry(artwork_new, level_node, type);
}
-#if 1
- if (level_node->level_group)
- DrawInitText(level_node->name, 150, FC_YELLOW);
-#endif
+ DrawInitText(level_node->name, 150, FC_YELLOW);
if (level_node->node_group != NULL)
LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
}
}
-void LoadLevelArtworkInfo()
+void LoadLevelArtworkInfo(void)
{
+ print_timestamp_init("LoadLevelArtworkInfo");
+
DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
+ print_timestamp_time("DrawTimeText");
+
LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
+ print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
+ print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
+ print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
SaveArtworkInfoCache();
- /* needed for reloading level artwork not known at ealier stage */
-
- if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
- {
- artwork.gfx_current =
- getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
- if (artwork.gfx_current == NULL)
- artwork.gfx_current =
- getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
- if (artwork.gfx_current == NULL)
- artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
- }
+ print_timestamp_time("SaveArtworkInfoCache");
- if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
- {
- artwork.snd_current =
- getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
- if (artwork.snd_current == NULL)
- artwork.snd_current =
- getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
- if (artwork.snd_current == NULL)
- artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
- }
+ // needed for reloading level artwork not known at ealier stage
+ ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
+ ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
+ ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
- if (!strEqual(artwork.mus_current_identifier, setup.music_set))
- {
- artwork.mus_current =
- getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
- if (artwork.mus_current == NULL)
- artwork.mus_current =
- getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
- if (artwork.mus_current == NULL)
- artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
- }
+ print_timestamp_time("getTreeInfoFromIdentifier");
sortTreeInfo(&artwork.gfx_first);
sortTreeInfo(&artwork.snd_first);
sortTreeInfo(&artwork.mus_first);
-#if 0
+ print_timestamp_time("sortTreeInfo");
+
+#if ENABLE_UNUSED_CODE
dumpTreeInfo(artwork.gfx_first, 0);
dumpTreeInfo(artwork.snd_first, 0);
dumpTreeInfo(artwork.mus_first, 0);
#endif
+
+ print_timestamp_done("LoadLevelArtworkInfo");
+}
+
+static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
+ char *tree_subdir_new, int type)
+{
+ if (tree_node_old == NULL)
+ {
+ if (type == TREE_TYPE_LEVEL_DIR)
+ {
+ // get level info tree node of personal user level set
+ tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
+
+ // this may happen if "setup.internal.create_user_levelset" is FALSE
+ // or if file "levelinfo.conf" is missing in personal user level set
+ if (tree_node_old == NULL)
+ tree_node_old = leveldir_first->node_group;
+ }
+ else
+ {
+ // get artwork info tree node of first artwork set
+ tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
+ }
+ }
+
+ if (tree_dir == NULL)
+ tree_dir = TREE_USERDIR(type);
+
+ if (tree_node_old == NULL ||
+ tree_dir == NULL ||
+ tree_subdir_new == NULL) // should not happen
+ return FALSE;
+
+ int draw_deactivation_mask = GetDrawDeactivationMask();
+
+ // override draw deactivation mask (temporarily disable drawing)
+ SetDrawDeactivationMask(REDRAW_ALL);
+
+ if (type == TREE_TYPE_LEVEL_DIR)
+ {
+ // load new level set config and add it next to first user level set
+ LoadLevelInfoFromLevelConf(&tree_node_old->next,
+ tree_node_old->node_parent,
+ tree_dir, tree_subdir_new);
+ }
+ else
+ {
+ // load new artwork set config and add it next to first artwork set
+ LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
+ tree_node_old->node_parent,
+ tree_dir, tree_subdir_new, type);
+ }
+
+ // set draw deactivation mask to previous value
+ SetDrawDeactivationMask(draw_deactivation_mask);
+
+ // get first node of level or artwork info tree
+ TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
+
+ // get tree info node of newly added level or artwork set
+ TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
+ tree_subdir_new);
+
+ if (tree_node_new == NULL) // should not happen
+ return FALSE;
+
+ // correct top link and parent node link of newly created tree node
+ tree_node_new->node_top = tree_node_old->node_top;
+ tree_node_new->node_parent = tree_node_old->node_parent;
+
+ // sort tree info to adjust position of newly added tree set
+ sortTreeInfo(tree_node_first);
+
+ return TRUE;
+}
+
+void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
+ char *tree_subdir_new, int type)
+{
+ if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
+ Fail("internal tree info structure corrupted -- aborting");
}
-static void SaveUserLevelInfo()
+void AddUserLevelSetToLevelInfo(char *level_subdir_new)
+{
+ AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
+}
+
+char *getArtworkIdentifierForUserLevelSet(int type)
+{
+ char *classic_artwork_set = getClassicArtworkSet(type);
+
+ // check for custom artwork configured in "levelinfo.conf"
+ char *leveldir_artwork_set =
+ *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
+ boolean has_leveldir_artwork_set =
+ (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
+ classic_artwork_set));
+
+ // check for custom artwork in sub-directory "graphics" etc.
+ TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
+ char *leveldir_identifier = leveldir_current->identifier;
+ boolean has_artwork_subdir =
+ (getTreeInfoFromIdentifier(artwork_first_node,
+ leveldir_identifier) != NULL);
+
+ return (has_leveldir_artwork_set ? leveldir_artwork_set :
+ has_artwork_subdir ? leveldir_identifier :
+ classic_artwork_set);
+}
+
+TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
+{
+ char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
+ TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
+ TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
+
+ if (ti == NULL)
+ {
+ ti = getTreeInfoFromIdentifier(artwork_first_node,
+ ARTWORK_DEFAULT_SUBDIR(type));
+ if (ti == NULL)
+ Fail("cannot find default graphics -- should not happen");
+ }
+
+ return ti;
+}
+
+boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
+{
+ char *graphics_set =
+ getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
+ char *sounds_set =
+ getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
+ char *music_set =
+ getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
+
+ return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
+ !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
+ !strEqual(music_set, MUS_CLASSIC_SUBDIR));
+}
+
+boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
+ char *level_author, int num_levels)
+{
+ char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
+ char *filename_tmp = getStringCat2(filename, ".tmp");
+ FILE *file = NULL;
+ FILE *file_tmp = NULL;
+ char line[MAX_LINE_LEN];
+ boolean success = FALSE;
+ LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
+ level_subdir);
+ // update values in level directory tree
+
+ if (level_name != NULL)
+ setString(&leveldir->name, level_name);
+
+ if (level_author != NULL)
+ setString(&leveldir->author, level_author);
+
+ if (num_levels != -1)
+ leveldir->levels = num_levels;
+
+ // update values that depend on other values
+
+ setString(&leveldir->name_sorting, leveldir->name);
+
+ leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
+
+ // sort order of level sets may have changed
+ sortTreeInfo(&leveldir_first);
+
+ if ((file = fopen(filename, MODE_READ)) &&
+ (file_tmp = fopen(filename_tmp, MODE_WRITE)))
+ {
+ while (fgets(line, MAX_LINE_LEN, file))
+ {
+ if (strPrefix(line, "name:") && level_name != NULL)
+ fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
+ else if (strPrefix(line, "author:") && level_author != NULL)
+ fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
+ else if (strPrefix(line, "levels:") && num_levels != -1)
+ fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
+ else
+ fputs(line, file_tmp);
+ }
+
+ success = TRUE;
+ }
+
+ if (file)
+ fclose(file);
+
+ if (file_tmp)
+ fclose(file_tmp);
+
+ if (success)
+ success = (rename(filename_tmp, filename) == 0);
+
+ free(filename);
+ free(filename_tmp);
+
+ return success;
+}
+
+boolean CreateUserLevelSet(char *level_subdir, char *level_name,
+ char *level_author, int num_levels,
+ boolean use_artwork_set)
{
LevelDirTree *level_info;
char *filename;
FILE *file;
int i;
- filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
+ // create user level sub-directory, if needed
+ createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
+
+ filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
if (!(file = fopen(filename, MODE_WRITE)))
{
- Error(ERR_WARN, "cannot write level info file '%s'", filename);
+ Warn("cannot write level info file '%s'", filename);
+
free(filename);
- return;
+
+ return FALSE;
}
level_info = newTreeInfo();
- /* always start with reliable default values */
+ // always start with reliable default values
setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
- setString(&level_info->name, getLoginName());
- setString(&level_info->author, getRealName());
- level_info->levels = 100;
+ setString(&level_info->name, level_name);
+ setString(&level_info->author, level_author);
+ level_info->levels = num_levels;
level_info->first_level = 1;
+ level_info->sort_priority = LEVELCLASS_PRIVATE_START;
+ level_info->readonly = FALSE;
+
+ if (use_artwork_set)
+ {
+ level_info->graphics_set =
+ getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
+ level_info->sounds_set =
+ getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
+ level_info->music_set =
+ getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
+ }
token_value_position = TOKEN_VALUE_POSITION_SHORT;
- fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
- getCookie("LEVELINFO")));
+ fprintFileHeader(file, LEVELINFO_FILENAME);
ldi = *level_info;
for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
if (i == LEVELINFO_TOKEN_NAME ||
i == LEVELINFO_TOKEN_AUTHOR ||
i == LEVELINFO_TOKEN_LEVELS ||
- i == LEVELINFO_TOKEN_FIRST_LEVEL)
+ i == LEVELINFO_TOKEN_FIRST_LEVEL ||
+ i == LEVELINFO_TOKEN_SORT_PRIORITY ||
+ i == LEVELINFO_TOKEN_READONLY ||
+ (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
+ i == LEVELINFO_TOKEN_SOUNDS_SET ||
+ i == LEVELINFO_TOKEN_MUSIC_SET)))
fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
- /* just to make things nicer :) */
- if (i == LEVELINFO_TOKEN_AUTHOR)
+ // just to make things nicer :)
+ if (i == LEVELINFO_TOKEN_AUTHOR ||
+ i == LEVELINFO_TOKEN_FIRST_LEVEL ||
+ (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
fprintf(file, "\n");
}
freeTreeInfo(level_info);
free(filename);
+
+ return TRUE;
+}
+
+static void SaveUserLevelInfo(void)
+{
+ CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
}
char *getSetupValue(int type, void *value)
strcpy(value_string, (*(boolean *)value ? "on" : "off"));
break;
+ case TYPE_SWITCH3:
+ strcpy(value_string, (*(int *)value == AUTO ? "auto" :
+ *(int *)value == FALSE ? "off" : "on"));
+ break;
+
case TYPE_YES_NO:
strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
break;
+ case TYPE_YES_NO_AUTO:
+ strcpy(value_string, (*(int *)value == AUTO ? "auto" :
+ *(int *)value == FALSE ? "no" : "yes"));
+ break;
+
case TYPE_ECS_AGA:
strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
break;
strcpy(value_string, *(char **)value);
break;
+ case TYPE_PLAYER:
+ sprintf(value_string, "player_%d", *(int *)value + 1);
+ break;
+
default:
value_string[0] = '\0';
break;
char *token_text = token_info[token_nr].text;
char *value_string = getSetupValue(token_type, setup_value);
- /* build complete token string */
+ // build complete token string
sprintf(token_string, "%s%s", prefix, token_text);
- /* build setup entry line */
+ // build setup entry line
line = getFormattedSetupEntry(token_string, value_string);
if (token_type == TYPE_KEY_X11)
Key key = *(Key *)setup_value;
char *keyname = getKeyNameFromKey(key);
- /* add comment, if useful */
+ // add comment, if useful
if (!strEqual(keyname, "(undefined)") &&
!strEqual(keyname, "(unknown)"))
{
- /* add at least one whitespace */
+ // add at least one whitespace
strcat(line, " ");
for (i = strlen(line); i < token_comment_position; i++)
strcat(line, " ");
return line;
}
-void LoadLevelSetup_LastSeries()
+void LoadLevelSetup_LastSeries(void)
{
- /* ----------------------------------------------------------------------- */
- /* ~/.<program>/levelsetup.conf */
- /* ----------------------------------------------------------------------- */
+ // --------------------------------------------------------------------------
+ // ~/.<program>/levelsetup.conf
+ // --------------------------------------------------------------------------
char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
SetupFileHash *level_setup_hash = NULL;
- /* always start with reliable default values */
+ // always start with reliable default values
leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
+ if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
+ {
+ leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
+ DEFAULT_LEVELSET);
+ if (leveldir_current == NULL)
+ leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
+ }
+
if ((level_setup_hash = loadSetupFileHash(filename)))
{
char *last_level_series =
if (leveldir_current == NULL)
leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
- checkSetupFileHashIdentifier(level_setup_hash, filename,
- getCookie("LEVELSETUP"));
-
freeSetupFileHash(level_setup_hash);
}
else
- Error(ERR_WARN, "using default setup values");
+ {
+ Debug("setup", "using default setup values");
+ }
free(filename);
}
-void SaveLevelSetup_LastSeries()
+static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
{
- /* ----------------------------------------------------------------------- */
- /* ~/.<program>/levelsetup.conf */
- /* ----------------------------------------------------------------------- */
+ // --------------------------------------------------------------------------
+ // ~/.<program>/levelsetup.conf
+ // --------------------------------------------------------------------------
+
+ // check if the current level directory structure is available at this point
+ if (leveldir_current == NULL)
+ return;
char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
char *level_subdir = leveldir_current->subdir;
if (!(file = fopen(filename, MODE_WRITE)))
{
- Error(ERR_WARN, "cannot write setup file '%s'", filename);
+ Warn("cannot write setup file '%s'", filename);
+
free(filename);
+
return;
}
- fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
- getCookie("LEVELSETUP")));
+ fprintFileHeader(file, LEVELSETUP_FILENAME);
+
+ if (deactivate_last_level_series)
+ fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
+
fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
level_subdir));
free(filename);
}
-static void checkSeriesInfo()
+void SaveLevelSetup_LastSeries(void)
+{
+ SaveLevelSetup_LastSeries_Ext(FALSE);
+}
+
+void SaveLevelSetup_LastSeries_Deactivate(void)
+{
+ SaveLevelSetup_LastSeries_Ext(TRUE);
+}
+
+static void checkSeriesInfo(void)
{
static char *level_directory = NULL;
- DIR *dir;
- struct dirent *dir_entry;
+ Directory *dir;
+#if 0
+ DirectoryEntry *dir_entry;
+#endif
- /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
+ checked_free(level_directory);
+
+ // check for more levels besides the 'levels' field of 'levelinfo.conf'
level_directory = getPath2((leveldir_current->in_user_dir ?
getUserLevelDir(NULL) :
options.level_directory),
leveldir_current->fullpath);
- if ((dir = opendir(level_directory)) == NULL)
+ if ((dir = openDirectory(level_directory)) == NULL)
{
- Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
+ Warn("cannot read level directory '%s'", level_directory);
+
return;
}
- while ((dir_entry = readdir(dir)) != NULL) /* last directory entry */
+#if 0
+ while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
{
- if (strlen(dir_entry->d_name) > 4 &&
- dir_entry->d_name[3] == '.' &&
- strEqual(&dir_entry->d_name[4], LEVELFILE_EXTENSION))
+ if (strlen(dir_entry->basename) > 4 &&
+ dir_entry->basename[3] == '.' &&
+ strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
{
char levelnum_str[4];
int levelnum_value;
- strncpy(levelnum_str, dir_entry->d_name, 3);
+ strncpy(levelnum_str, dir_entry->basename, 3);
levelnum_str[3] = '\0';
levelnum_value = atoi(levelnum_str);
-#if 0
if (levelnum_value < leveldir_current->first_level)
{
- Error(ERR_WARN, "additional level %d found", levelnum_value);
+ Warn("additional level %d found", levelnum_value);
+
leveldir_current->first_level = levelnum_value;
}
else if (levelnum_value > leveldir_current->last_level)
{
- Error(ERR_WARN, "additional level %d found", levelnum_value);
+ Warn("additional level %d found", levelnum_value);
+
leveldir_current->last_level = levelnum_value;
}
-#endif
}
}
+#endif
- closedir(dir);
+ closeDirectory(dir);
}
-void LoadLevelSetup_SeriesInfo()
+void LoadLevelSetup_SeriesInfo(void)
{
char *filename;
SetupFileHash *level_setup_hash = NULL;
char *level_subdir = leveldir_current->subdir;
+ int i;
- /* always start with reliable default values */
+ // always start with reliable default values
level_nr = leveldir_current->first_level;
- checkSeriesInfo(leveldir_current);
+ for (i = 0; i < MAX_LEVELS; i++)
+ {
+ LevelStats_setPlayed(i, 0);
+ LevelStats_setSolved(i, 0);
+ }
+
+ checkSeriesInfo();
- /* ----------------------------------------------------------------------- */
- /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
- /* ----------------------------------------------------------------------- */
+ // --------------------------------------------------------------------------
+ // ~/.<program>/levelsetup/<level series>/levelsetup.conf
+ // --------------------------------------------------------------------------
level_subdir = leveldir_current->subdir;
{
char *token_value;
+ // get last played level in this level set
+
token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
if (token_value)
level_nr = leveldir_current->last_level;
}
+ // get handicap level in this level set
+
token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
if (token_value)
leveldir_current->handicap_level = level_nr;
}
- checkSetupFileHashIdentifier(level_setup_hash, filename,
- getCookie("LEVELSETUP"));
+ // get number of played and solved levels in this level set
+
+ BEGIN_HASH_ITERATION(level_setup_hash, itr)
+ {
+ char *token = HASH_ITERATION_TOKEN(itr);
+ char *value = HASH_ITERATION_VALUE(itr);
+
+ if (strlen(token) == 3 &&
+ token[0] >= '0' && token[0] <= '9' &&
+ token[1] >= '0' && token[1] <= '9' &&
+ token[2] >= '0' && token[2] <= '9')
+ {
+ int level_nr = atoi(token);
+
+ if (value != NULL)
+ LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
+
+ value = strchr(value, ' ');
+
+ if (value != NULL)
+ LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
+ }
+ }
+ END_HASH_ITERATION(hash, itr)
freeSetupFileHash(level_setup_hash);
}
else
- Error(ERR_WARN, "using default setup values");
+ {
+ Debug("setup", "using default setup values");
+ }
free(filename);
}
-void SaveLevelSetup_SeriesInfo()
+void SaveLevelSetup_SeriesInfo(void)
{
char *filename;
char *level_subdir = leveldir_current->subdir;
char *level_nr_str = int2str(level_nr, 0);
char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
FILE *file;
+ int i;
- /* ----------------------------------------------------------------------- */
- /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
- /* ----------------------------------------------------------------------- */
+ // --------------------------------------------------------------------------
+ // ~/.<program>/levelsetup/<level series>/levelsetup.conf
+ // --------------------------------------------------------------------------
InitLevelSetupDirectory(level_subdir);
if (!(file = fopen(filename, MODE_WRITE)))
{
- Error(ERR_WARN, "cannot write setup file '%s'", filename);
+ Warn("cannot write setup file '%s'", filename);
+
free(filename);
+
return;
}
- fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
- getCookie("LEVELSETUP")));
+ fprintFileHeader(file, LEVELSETUP_FILENAME);
+
fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
level_nr_str));
- fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
- handicap_level_str));
+ fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
+ handicap_level_str));
+
+ for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
+ i++)
+ {
+ if (LevelStats_getPlayed(i) > 0 ||
+ LevelStats_getSolved(i) > 0)
+ {
+ char token[16];
+ char value[16];
+
+ sprintf(token, "%03d", i);
+ sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
+
+ fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
+ }
+ }
+
+ fclose(file);
+
+ SetFilePermissions(filename, PERMS_PRIVATE);
+
+ free(filename);
+}
+
+int LevelStats_getPlayed(int nr)
+{
+ return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
+}
+
+int LevelStats_getSolved(int nr)
+{
+ return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
+}
+
+void LevelStats_setPlayed(int nr, int value)
+{
+ if (nr >= 0 && nr < MAX_LEVELS)
+ level_stats[nr].played = value;
+}
+
+void LevelStats_setSolved(int nr, int value)
+{
+ if (nr >= 0 && nr < MAX_LEVELS)
+ level_stats[nr].solved = value;
+}
+
+void LevelStats_incPlayed(int nr)
+{
+ if (nr >= 0 && nr < MAX_LEVELS)
+ level_stats[nr].played++;
+}
+
+void LevelStats_incSolved(int nr)
+{
+ if (nr >= 0 && nr < MAX_LEVELS)
+ level_stats[nr].solved++;
+}
+
+void LoadUserSetup(void)
+{
+ // --------------------------------------------------------------------------
+ // ~/.<program>/usersetup.conf
+ // --------------------------------------------------------------------------
+
+ char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
+ SetupFileHash *user_setup_hash = NULL;
+
+ // always start with reliable default values
+ user.nr = 0;
+
+ if ((user_setup_hash = loadSetupFileHash(filename)))
+ {
+ char *token_value;
+
+ // get last selected user number
+ token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
+
+ if (token_value)
+ user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
+
+ freeSetupFileHash(user_setup_hash);
+ }
+ else
+ {
+ Debug("setup", "using default setup values");
+ }
+
+ free(filename);
+}
+
+void SaveUserSetup(void)
+{
+ // --------------------------------------------------------------------------
+ // ~/.<program>/usersetup.conf
+ // --------------------------------------------------------------------------
+
+ char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
+ FILE *file;
+
+ InitMainUserDataDirectory();
+
+ if (!(file = fopen(filename, MODE_WRITE)))
+ {
+ Warn("cannot write setup file '%s'", filename);
+
+ free(filename);
+
+ return;
+ }
+
+ fprintFileHeader(file, USERSETUP_FILENAME);
+ fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
+ i_to_a(user.nr)));
fclose(file);
SetFilePermissions(filename, PERMS_PRIVATE);