Mercurial > molko
diff src/libmlk-core/core/sys.c @ 320:8f9937403749
misc: improve loading of data
author | David Demelier <markand@malikania.fr> |
---|---|
date | Fri, 01 Oct 2021 20:30:00 +0200 |
parents | libmlk-core/core/sys.c@1a6125ffebff |
children | 7d7991f97acf |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/libmlk-core/core/sys.c Fri Oct 01 20:30:00 2021 +0200 @@ -0,0 +1,300 @@ +/* + * sys.c -- system routines + * + * Copyright (c) 2020-2021 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include <sys/stat.h> +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <limits.h> + +#if defined(_WIN32) +# include <shlwapi.h> +# include <windows.h> +#else +# include <sys/stat.h> +# include <errno.h> +# include <string.h> +#endif + +#include <SDL.h> +#include <SDL_image.h> +#include <SDL_mixer.h> +#include <SDL_ttf.h> + +#include <port/port.h> + +#include "error.h" +#include "sound.h" +#include "sys.h" + +static struct { + char organization[128]; + char name[128]; +} info = { + .organization = "fr.malikania", + .name = "molko" +}; + +static struct { + char bindir[PATH_MAX]; + char datadir[PATH_MAX]; + char localedir[PATH_MAX]; +} paths; + +static inline char * +normalize(char *str) +{ + for (char *p = str; *p; ++p) + if (*p == '\\') + *p = '/'; + + return str; +} + +static inline int +absolute(const char *path) +{ +#if defined(_WIN32) + return !PathIsRelativeA(path); +#else + /* Assuming UNIX like. */ + if (path[0] == '/') + return 1; + + return 0; +#endif +} + +static const char * +system_directory(const char *whichdir) +{ + static char path[PATH_MAX]; + static char ret[PATH_MAX]; + char *base, *binsect; + + /* + * Some system does not provide support (shame on you OpenBSD) + * to the executable path. In that case we use PREFIX+<dir> + * instead unless <dir> is already absolute. + */ + + /* Component (e.g. MLK_BINDIR is set to /bin), return immediately. */ + if (absolute(whichdir)) + return whichdir; + + /* Determine base executable path. */ + if (!(base = SDL_GetBasePath())) { + if (absolute(whichdir)) + strlcpy(ret, whichdir, sizeof (ret)); + else + snprintf(ret, sizeof (ret), "%s/%s", MLK_PREFIX, whichdir); + } else { + /* + * Decompose the path to the given special directory by + * computing relative directory to it from where the + * binary is located. + * + * Example: + * + * PREFIX/bin/mlk + * PREFIX/share/mlk-adventure + * + * The path to the data is ../share/molko starting from + * the binary. + * + * Put the base path into the path and remove the value + * of MLK_BINDIR. + * + * Example: + * from: /usr/local/bin + * to: /usr/local + */ + strlcpy(path, base, sizeof (path)); + SDL_free(base); + + if ((binsect = strstr(path, MLK_BINDIR))) + *binsect = '\0'; + + snprintf(ret, sizeof (ret), "%s%s", path, whichdir); + } + + return normalize(ret); +} + +static const char * +user_directory(enum sys_dir kind) +{ + /* Kept for future use. */ + (void)kind; + + static char path[PATH_MAX]; + char *pref; + + if ((pref = SDL_GetPrefPath(info.organization, info.name))) { + strlcpy(path, pref, sizeof (path)); + SDL_free(pref); + } else + strlcpy(path, "./", sizeof (path)); + + return path; +} + +static inline int +mkpath(const char *path) +{ +#ifdef _WIN32 + /* TODO: add error using the convenient FormatMessage function. */ + if (!CreateDirectoryA(path, NULL) && GetLastError() != ERROR_ALREADY_EXISTS) + return errorf("unable to create directory: %s", path); +#else + if (mkdir(path, 0755) < 0 && errno != EEXIST) + return errorf("%s", strerror(errno)); +#endif + + return 0; +} + +static inline void +set_bindir(void) +{ + strlcpy(paths.bindir, system_directory(MLK_BINDIR), sizeof (paths.bindir)); +} + +static void +set_datadir(void) +{ + char *base, test[PATH_MAX]; + struct stat st; + + /* + * For convenient purposes, if we're running executables directly from + * source tree, check for libmlk-data presence and use it. Otherwise + * use standard system directory with the project named concatenated. + */ + if ((base = SDL_GetBasePath())) { + snprintf(test, sizeof (test), "%ssrc/libmlk-data", base); + + if (stat(test, &st) == 0 && S_ISDIR(st.st_mode)) { + strlcpy(paths.datadir, test, sizeof (paths.datadir)); + normalize(paths.datadir); + } + + free(base); + } + + /* Not found, use standard. */ + if (!paths.datadir[0]) + snprintf(paths.datadir, sizeof (paths.datadir), "%s/%s", + system_directory(MLK_DATADIR), info.name); +} + +static inline void +set_localedir(void) +{ + strlcpy(paths.localedir, system_directory(MLK_BINDIR), sizeof (paths.localedir)); +} + +int +sys_init(const char *organization, const char *name) +{ +#if defined(__MINGW64__) + /* On MinGW buffering leads to painful debugging. */ + setbuf(stderr, NULL); + setbuf(stdout, NULL); +#endif + set_bindir(); + set_datadir(); + set_localedir(); + + strlcpy(info.organization, organization, sizeof (info.organization)); + strlcpy(info.name, name, sizeof (info.name)); + + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) + return errorf("%s", SDL_GetError()); + if (IMG_Init(IMG_INIT_PNG) != IMG_INIT_PNG) + return errorf("%s", SDL_GetError()); + if (TTF_Init() < 0) + return errorf("%s", SDL_GetError()); + if (Mix_Init(MIX_INIT_OGG) != MIX_INIT_OGG) + return errorf("%s", SDL_GetError()); + if (Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 4096) < 0) + return errorf("%s", SDL_GetError()); + + Mix_AllocateChannels(SOUND_CHANNELS_MAX); + + return 0; +} + +const char * +sys_dir(enum sys_dir kind) +{ + switch (kind) { + case SYS_DIR_BIN: + return paths.bindir; + case SYS_DIR_DATA: + return paths.datadir; + case SYS_DIR_LOCALE: + return paths.localedir; + default: + return user_directory(kind); + } +} + +int +sys_mkdir(const char *directory) +{ + char path[PATH_MAX], *p; + + /* Copy the directory to normalize and iterate over '/'. */ + strlcpy(path, directory, sizeof (path)); + normalize(path); + +#if defined(_WIN32) + /* Remove drive letter that we don't need. */ + if ((p = strchr(path, ':'))) + ++p; + else + p = path; +#else + p = path; +#endif + + for (p = p + 1; *p; ++p) { + if (*p == '/') { + *p = 0; + + if (mkpath(path) < 0) + return -1; + + *p = '/'; + } + } + + return mkpath(path); +} + +void +sys_finish(void) +{ + Mix_Quit(); + TTF_Quit(); + IMG_Quit(); + SDL_Quit(); +}