WIP virtual filesystem
This commit is contained in:
parent
02f1c935ff
commit
471f30083e
49 changed files with 1496 additions and 348 deletions
|
@ -11,7 +11,6 @@ else()
|
|||
endif()
|
||||
|
||||
option(RELATIVE "Use only relative paths to the executable and install everything in the same directory." ${RELATIVE_DEFAULT})
|
||||
option(USE_SDL2_PATHS "Use the SDL2 filesystem API to determine where to save settings." ON)
|
||||
option(NO_AUDIO "Build without audio support" OFF)
|
||||
option(DEBUG_USE_UBSAN "Enable the Undefined Behaviour Sanitizer (UBSan) in debug builds. Only disable if the compiler or target platform doesn't support it." ON)
|
||||
option(DEBUG_USE_ASAN "Enable the Address Sanitizer (ASan) and leak detection in debug builds." OFF)
|
||||
|
|
|
@ -87,16 +87,14 @@ set(SRCs
|
|||
rwops/rwops_zlib.c
|
||||
rwops/rwops_segment.c
|
||||
rwops/rwops_autobuf.c
|
||||
vfs/private.c
|
||||
vfs/public.c
|
||||
vfs/pathutil.c
|
||||
vfs/syspath_posix.c # TODO: write a win32 version of this
|
||||
vfs/union.c
|
||||
vfs/vdir.c
|
||||
)
|
||||
|
||||
if(USE_SDL2_PATHS)
|
||||
set(SRCs ${SRCs} paths/sdl.c)
|
||||
elseif(RELATIVE)
|
||||
set(SRCs ${SRCs} paths/relative.c)
|
||||
else()
|
||||
set(SRCs ${SRCs} paths/static.c)
|
||||
endif()
|
||||
|
||||
if(NO_AUDIO)
|
||||
set(SRCs ${SRCs}
|
||||
audio_null.c
|
||||
|
|
|
@ -67,9 +67,7 @@ static void bgm_cfg_volume_callback(ConfigIndex idx, ConfigValue v) {
|
|||
}
|
||||
|
||||
static void load_bgm_descriptions(void) {
|
||||
char *fullname = strjoin(get_prefix(), "bgm/bgm.conf", NULL);
|
||||
bgm_descriptions = parse_keyvalue_file(fullname, 16);
|
||||
free(fullname);
|
||||
bgm_descriptions = parse_keyvalue_file(BGM_PATH_PREFIX "bgm.conf", HT_DYNAMIC_SIZE);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,8 +6,6 @@
|
|||
*/
|
||||
|
||||
#include <SDL_mixer.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "audio.h"
|
||||
#include "global.h"
|
||||
|
@ -111,9 +109,8 @@ void audio_backend_set_bgm_volume(float gain) {
|
|||
char* audio_mixer_sound_path(const char *prefix, const char *name) {
|
||||
for(const char **ext = mixer_audio_exts; *ext; ++ext) {
|
||||
char *p = strjoin(prefix, name, *ext, NULL);
|
||||
struct stat statbuf;
|
||||
|
||||
if(!stat(p, &statbuf)) {
|
||||
if(vfs_query(p).exists) {
|
||||
return p;
|
||||
}
|
||||
|
||||
|
|
|
@ -41,6 +41,7 @@ int cli_args(int argc, char **argv, CLIAction *a) {
|
|||
{{"shotmode", required_argument, 0, 's'}, "Select a shotmode (marisaA/youmuA/marisaB/youmuB)", "SMODE"},
|
||||
{{"frameskip", optional_argument, 0, 'f'}, "Disable FPS limiter, render only every %s frame", "FRAME"},
|
||||
{{"dumpstages", no_argument, 0, 'u'}, "Print a list of all stages in the game", 0},
|
||||
{{"vfs-tree", required_argument, 0, 't'}, "Print the virtual filesystem tree starting from %s", "PATH"},
|
||||
#endif
|
||||
{{"help", no_argument, 0, 'h'}, "Display this help."},
|
||||
{{0,0,0,0},0,0}
|
||||
|
@ -144,6 +145,10 @@ int cli_args(int argc, char **argv, CLIAction *a) {
|
|||
}
|
||||
}
|
||||
break;
|
||||
case 't':
|
||||
a->type = CLI_DumpVFSTree,
|
||||
a->filename = strdup(optarg ? optarg : "");
|
||||
break;
|
||||
default:
|
||||
log_fatal("Unknown option (this shouldn’t happen)");
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ typedef enum {
|
|||
CLI_PlayReplay,
|
||||
CLI_SelectStage,
|
||||
CLI_DumpStages,
|
||||
CLI_DumpVFSTree,
|
||||
CLI_Quit,
|
||||
} CLIActionType;
|
||||
|
||||
|
|
35
src/config.c
35
src/config.c
|
@ -10,7 +10,6 @@
|
|||
|
||||
#include "config.h"
|
||||
#include "global.h"
|
||||
#include "paths/native.h"
|
||||
|
||||
static bool config_initialized = false;
|
||||
|
||||
|
@ -263,25 +262,18 @@ static void config_delete_unknown_entries(void) {
|
|||
delete_all_elements((void**)&unknowndefs, config_delete_unknown_entry);
|
||||
}
|
||||
|
||||
static char* config_path(const char *filename) {
|
||||
return strjoin(get_config_path(), "/", filename, NULL);
|
||||
}
|
||||
|
||||
static SDL_RWops* config_open(const char *filename, const char *mode) {
|
||||
char *buf = config_path(filename);
|
||||
SDL_RWops *out = SDL_RWFromFile(buf, mode);
|
||||
|
||||
free(buf);
|
||||
static SDL_RWops* config_open(const char *filename, VFSOpenMode mode) {
|
||||
SDL_RWops *out = vfs_open(CONFIG_FILE, mode);
|
||||
|
||||
if(!out) {
|
||||
log_warn("SDL_RWFromFile() failed: %s", SDL_GetError());
|
||||
log_warn("VFS error: %s", vfs_get_error());
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
void config_save(const char *filename) {
|
||||
SDL_RWops *out = config_open(filename, "w");
|
||||
void config_save(void) {
|
||||
SDL_RWops *out = config_open(CONFIG_FILE, VFS_MODE_WRITE);
|
||||
ConfigEntry *e = configdefs;
|
||||
|
||||
if(!out)
|
||||
|
@ -317,7 +309,10 @@ void config_save(const char *filename) {
|
|||
}
|
||||
|
||||
SDL_RWclose(out);
|
||||
log_info("Saved config '%s'", filename);
|
||||
|
||||
char *sp = vfs_syspath_or_repr(CONFIG_FILE);
|
||||
log_info("Saved config '%s'", sp);
|
||||
free(sp);
|
||||
}
|
||||
|
||||
#define INTOF(s) ((int)strtol(s, NULL, 10))
|
||||
|
@ -362,12 +357,14 @@ static void config_set(const char *key, const char *val, void *data) {
|
|||
#undef INTOF
|
||||
#undef FLOATOF
|
||||
|
||||
void config_load(const char *filename) {
|
||||
char *path = config_path(filename);
|
||||
void config_load(void) {
|
||||
config_init();
|
||||
log_info("Loading configuration from %s", path);
|
||||
if(!parse_keyvalue_file_cb(path, config_set, NULL)) {
|
||||
|
||||
char *sp = vfs_syspath_or_repr(CONFIG_FILE);
|
||||
log_info("Loading configuration from %s", sp);
|
||||
free(sp);
|
||||
|
||||
if(!parse_keyvalue_file_cb(CONFIG_FILE, config_set, NULL)) {
|
||||
log_warn("Errors occured while parsing the configuration file");
|
||||
}
|
||||
free(path);
|
||||
}
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
#include <SDL_keycode.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define CONFIG_FILE "storage/config"
|
||||
|
||||
/*
|
||||
Define these macros, then use CONFIGDEFS to expand them all for all config entries, or KEYDEFS for just keybindings.
|
||||
Don't forget to undef them afterwards.
|
||||
|
@ -180,8 +182,8 @@ KeyIndex config_key_from_gamepad_button(int btn);
|
|||
void config_reset(void);
|
||||
void config_init(void);
|
||||
void config_uninit(void);
|
||||
void config_load(const char *filename);
|
||||
void config_save(const char *filename);
|
||||
void config_load(void);
|
||||
void config_save(void);
|
||||
void config_set_callback(ConfigIndex idx, ConfigCallback callback);
|
||||
|
||||
#ifndef DEBUG
|
||||
|
|
|
@ -46,9 +46,6 @@
|
|||
#include "rwops/all.h"
|
||||
#include "cli.h"
|
||||
|
||||
#define FILE_PREFIX PREFIX "/share/taisei/"
|
||||
#define CONFIG_FILE "config"
|
||||
|
||||
enum {
|
||||
// defaults
|
||||
RESX = 800,
|
||||
|
|
114
src/main.c
114
src/main.c
|
@ -5,8 +5,6 @@
|
|||
* Copyright (C) 2011, Lukas Weber <laochailan@web.de>
|
||||
*/
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <errno.h>
|
||||
#include <locale.h>
|
||||
|
||||
#include "global.h"
|
||||
|
@ -14,18 +12,20 @@
|
|||
#include "audio.h"
|
||||
#include "stage.h"
|
||||
#include "menu/mainmenu.h"
|
||||
#include "paths/native.h"
|
||||
#include "gamepad.h"
|
||||
#include "resource/bgm.h"
|
||||
#include "progress.h"
|
||||
#include "hashtable.h"
|
||||
#include "log.h"
|
||||
#include "cli.h"
|
||||
#include "vfs/public.h"
|
||||
|
||||
void taisei_shutdown(void) {
|
||||
#define STATIC_RESOURCE_PREFIX PREFIX "/share/taisei/"
|
||||
|
||||
static void taisei_shutdown(void) {
|
||||
log_info("Shutting down");
|
||||
|
||||
config_save(CONFIG_FILE);
|
||||
config_save();
|
||||
progress_save();
|
||||
progress_unload();
|
||||
|
||||
|
@ -37,6 +37,7 @@ void taisei_shutdown(void) {
|
|||
gamepad_shutdown();
|
||||
stage_free_array();
|
||||
config_uninit();
|
||||
vfs_uninit();
|
||||
|
||||
log_info("Good bye");
|
||||
SDL_Quit();
|
||||
|
@ -44,25 +45,23 @@ void taisei_shutdown(void) {
|
|||
log_shutdown();
|
||||
}
|
||||
|
||||
void init_log(void) {
|
||||
const char *pref = get_config_path();
|
||||
char *logpath = strfmt("%s/%s", pref, "log.txt");
|
||||
|
||||
static void init_log(void) {
|
||||
LogLevel lvls_console = log_parse_levels(LOG_DEFAULT_LEVELS_CONSOLE, getenv("TAISEI_LOGLVLS_CONSOLE"));
|
||||
LogLevel lvls_stdout = lvls_console & log_parse_levels(LOG_DEFAULT_LEVELS_STDOUT, getenv("TAISEI_LOGLVLS_STDOUT"));
|
||||
LogLevel lvls_stderr = lvls_console & log_parse_levels(LOG_DEFAULT_LEVELS_STDERR, getenv("TAISEI_LOGLVLS_STDERR"));
|
||||
LogLevel lvls_file = log_parse_levels(LOG_DEFAULT_LEVELS_FILE, getenv("TAISEI_LOGLVLS_FILE"));
|
||||
LogLevel lvls_backtrace = log_parse_levels(LOG_DEFAULT_LEVELS_BACKTRACE, getenv("TAISEI_LOGLVLS_BACKTRACE"));
|
||||
|
||||
log_init(LOG_DEFAULT_LEVELS, lvls_backtrace);
|
||||
log_add_output(lvls_stdout, SDL_RWFromFP(stdout, false));
|
||||
log_add_output(lvls_stderr, SDL_RWFromFP(stderr, false));
|
||||
log_add_output(lvls_file, SDL_RWFromFile(logpath, "w"));
|
||||
|
||||
free(logpath);
|
||||
}
|
||||
|
||||
int run_tests(void) {
|
||||
static void init_log_file(void) {
|
||||
LogLevel lvls_file = log_parse_levels(LOG_DEFAULT_LEVELS_FILE, getenv("TAISEI_LOGLVLS_FILE"));
|
||||
log_add_output(lvls_file, vfs_open("storage/log.txt", VFS_MODE_WRITE));
|
||||
}
|
||||
|
||||
static int run_tests(void) {
|
||||
if(tsrand_test()) {
|
||||
return 1;
|
||||
}
|
||||
|
@ -101,6 +100,54 @@ static void init_sdl(void) {
|
|||
log_info("Using SDL %u.%u.%u", v.major, v.minor, v.patch);
|
||||
}
|
||||
|
||||
static void get_core_paths(char **res, char **storage) {
|
||||
#ifdef RELATIVE
|
||||
*res = SDL_GetBasePath();
|
||||
strappend(res, "data/");
|
||||
#else
|
||||
*res = strdup(STATIC_RESOURCE_PREFIX);
|
||||
#endif
|
||||
|
||||
*storage = SDL_GetPrefPath("", "taisei");
|
||||
}
|
||||
|
||||
static void init_vfs(bool silent) {
|
||||
char *res_path, *storage_path;
|
||||
get_core_paths(&res_path, &storage_path);
|
||||
|
||||
if(!silent) {
|
||||
log_info("Resource path: %s", res_path);
|
||||
log_info("Storage path: %s", storage_path);
|
||||
}
|
||||
|
||||
char *p = NULL;
|
||||
|
||||
struct mpoint_t {
|
||||
const char *dest; const char *syspath; bool mkdir;
|
||||
} mpts[] = {
|
||||
{"storage", storage_path, true},
|
||||
{"res", res_path, false},
|
||||
{"res", p = strfmt("%s/resources", storage_path), true},
|
||||
{NULL}
|
||||
};
|
||||
|
||||
vfs_init();
|
||||
vfs_create_union_mountpoint("res");
|
||||
|
||||
for(struct mpoint_t *mp = mpts; mp->dest; ++mp) {
|
||||
if(!vfs_mount_syspath(mp->dest, mp->syspath, mp->mkdir)) {
|
||||
log_fatal("Failed to mount '%s': %s", mp->dest, vfs_get_error());
|
||||
}
|
||||
}
|
||||
|
||||
vfs_mkdir_required("storage/replays");
|
||||
vfs_mkdir_required("storage/screenshots");
|
||||
|
||||
free(p);
|
||||
free(res_path);
|
||||
free(storage_path);
|
||||
}
|
||||
|
||||
static void log_lib_versions(void) {
|
||||
log_info("Compiled against zlib %s", ZLIB_VERSION);
|
||||
log_info("Using zlib %s", zlibVersion());
|
||||
|
@ -114,7 +161,6 @@ int main(int argc, char **argv) {
|
|||
Replay replay = {0};
|
||||
int replay_idx = 0;
|
||||
|
||||
init_paths();
|
||||
init_log();
|
||||
|
||||
if(run_tests()) {
|
||||
|
@ -136,7 +182,7 @@ int main(int argc, char **argv) {
|
|||
}
|
||||
return 0;
|
||||
} else if(a.type == CLI_PlayReplay) {
|
||||
if(!replay_load(&replay, a.filename, REPLAY_READ_ALL | REPLAY_READ_RAWPATH)) {
|
||||
if(!replay_load_syspath(&replay, a.filename, REPLAY_READ_ALL)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -144,26 +190,30 @@ int main(int argc, char **argv) {
|
|||
if(replay_idx < 0) {
|
||||
return 1;
|
||||
}
|
||||
} else if(a.type == CLI_DumpVFSTree) {
|
||||
init_vfs(true);
|
||||
|
||||
SDL_RWops *rwops = SDL_RWFromFP(stdout, false);
|
||||
|
||||
if(!rwops) {
|
||||
log_fatal("SDL_RWFromFP() failed: %s", SDL_GetError());
|
||||
}
|
||||
|
||||
if(!vfs_print_tree(rwops, a.filename)) {
|
||||
log_warn("VFS error: %s", vfs_get_error());
|
||||
SDL_RWclose(rwops);
|
||||
return 1;
|
||||
}
|
||||
|
||||
SDL_RWclose(rwops);
|
||||
return 0;
|
||||
}
|
||||
|
||||
free_cli_action(&a);
|
||||
init_vfs(false);
|
||||
init_log_file();
|
||||
|
||||
log_info("Content path: %s", get_prefix());
|
||||
log_info("Userdata path: %s", get_config_path());
|
||||
|
||||
MKDIR(get_config_path());
|
||||
MKDIR(get_screenshots_path());
|
||||
MKDIR(get_replays_path());
|
||||
|
||||
if(chdir(get_prefix())) {
|
||||
log_fatal("chdir() failed: %s", strerror(errno));
|
||||
} else {
|
||||
char cwd[1024]; // i don't care if this is not enough for you, getcwd is garbage
|
||||
getcwd(cwd, sizeof(cwd));
|
||||
log_info("Changed working directory to %s", cwd);
|
||||
}
|
||||
|
||||
config_load(CONFIG_FILE);
|
||||
config_load();
|
||||
|
||||
log_lib_versions();
|
||||
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
#include "global.h"
|
||||
#include "video.h"
|
||||
#include "stage.h"
|
||||
#include "paths/native.h"
|
||||
|
||||
void enter_options(MenuData *menu, void *arg) {
|
||||
MenuData m;
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
#include "options.h"
|
||||
#include "global.h"
|
||||
#include "video.h"
|
||||
#include "paths/native.h"
|
||||
|
||||
// --- Menu entry <-> config option binding stuff --- //
|
||||
|
||||
|
|
|
@ -6,14 +6,12 @@
|
|||
* Copyright (C) 2012, Alexeyew Andrew <https://github.com/nexAkari>
|
||||
*/
|
||||
|
||||
#include <dirent.h>
|
||||
#include <time.h>
|
||||
#include "global.h"
|
||||
#include "menu.h"
|
||||
#include "options.h"
|
||||
#include "mainmenu.h"
|
||||
#include "replayview.h"
|
||||
#include "paths/native.h"
|
||||
#include "plrmodes.h"
|
||||
#include "video.h"
|
||||
#include "common.h"
|
||||
|
@ -257,24 +255,24 @@ int replayview_cmp(const void *a, const void *b) {
|
|||
}
|
||||
|
||||
int fill_replayview_menu(MenuData *m) {
|
||||
DIR *dir = opendir(get_replays_path());
|
||||
struct dirent *e;
|
||||
VFSDir *dir = vfs_dir_open("storage/replays");
|
||||
const char *filename;
|
||||
int rpys = 0;
|
||||
|
||||
if(!dir) {
|
||||
log_warn("Could't read %s", get_replays_path());
|
||||
log_warn("VFS error: %s", vfs_get_error());
|
||||
return -1;
|
||||
}
|
||||
|
||||
char ext[5];
|
||||
snprintf(ext, 5, ".%s", REPLAY_EXTENSION);
|
||||
|
||||
while((e = readdir(dir))) {
|
||||
if(!strendswith(e->d_name, ext))
|
||||
while((filename = vfs_dir_read(dir))) {
|
||||
if(!strendswith(filename, ext))
|
||||
continue;
|
||||
|
||||
Replay *rpy = malloc(sizeof(Replay));
|
||||
if(!replay_load(rpy, e->d_name, REPLAY_READ_META)) {
|
||||
if(!replay_load(rpy, filename, REPLAY_READ_META)) {
|
||||
free(rpy);
|
||||
continue;
|
||||
}
|
||||
|
@ -283,14 +281,13 @@ int fill_replayview_menu(MenuData *m) {
|
|||
memset(ictx, 0, sizeof(ReplayviewItemContext));
|
||||
|
||||
ictx->replay = rpy;
|
||||
ictx->replayname = malloc(strlen(e->d_name) + 1);
|
||||
strcpy(ictx->replayname, e->d_name);
|
||||
ictx->replayname = strdup(filename);
|
||||
|
||||
add_menu_entry(m, " ", replayview_run, ictx)->transition = rpy->numstages < 2 ? TransFadeBlack : NULL;
|
||||
++rpys;
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
vfs_dir_close(dir);
|
||||
|
||||
if(m->entries) {
|
||||
qsort(m->entries, m->ecount, sizeof(MenuEntry), replayview_cmp);
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
/*
|
||||
* This software is licensed under the terms of the MIT-License
|
||||
* See COPYING for further information.
|
||||
* ---
|
||||
* Copyright (C) 2011, Lukas Weber <laochailan@web.de>
|
||||
*/
|
||||
|
||||
#ifndef NATIVE_H
|
||||
#define NATIVE_H
|
||||
|
||||
const char *get_prefix(void);
|
||||
const char *get_config_path(void);
|
||||
const char *get_screenshots_path(void);
|
||||
const char *get_replays_path(void);
|
||||
|
||||
void init_paths(void);
|
||||
|
||||
#endif
|
|
@ -1,27 +0,0 @@
|
|||
/*
|
||||
* This software is licensed under the terms of the MIT-License
|
||||
* See COPYING for further information.
|
||||
* ---
|
||||
* Copyright (C) 2011, Lukas Weber <laochailan@web.de>
|
||||
*/
|
||||
|
||||
#include "native.h"
|
||||
|
||||
const char *get_prefix(void) {
|
||||
return "./data/";
|
||||
}
|
||||
|
||||
const char *get_config_path(void) {
|
||||
return ".";
|
||||
}
|
||||
|
||||
const char *get_screenshots_path(void) {
|
||||
return "./screenshots";
|
||||
}
|
||||
|
||||
const char *get_replays_path(void) {
|
||||
return "./replays";
|
||||
}
|
||||
|
||||
void init_paths(void) {
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
/*
|
||||
* This software is licensed under the terms of the MIT-License
|
||||
* See COPYING for further information.
|
||||
* ---
|
||||
* Copyright (C) 2011, Lukas Weber <laochailan@web.de>
|
||||
*/
|
||||
|
||||
#include "native.h"
|
||||
#include "global.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define DATA_DIR "data/"
|
||||
|
||||
#define SCR_DIR "screenshots"
|
||||
#define RPY_DIR "replays"
|
||||
|
||||
char *content_path;
|
||||
char *conf_path;
|
||||
char *scr_path;
|
||||
char *rpy_path;
|
||||
|
||||
const char *get_prefix(void) {
|
||||
return content_path;
|
||||
}
|
||||
|
||||
const char *get_config_path(void) {
|
||||
return conf_path;
|
||||
}
|
||||
|
||||
const char *get_screenshots_path(void) {
|
||||
return scr_path;
|
||||
}
|
||||
|
||||
const char *get_replays_path(void) {
|
||||
return rpy_path;
|
||||
}
|
||||
|
||||
void init_paths(void) {
|
||||
#ifdef RELATIVE
|
||||
char *basedir = SDL_GetBasePath();
|
||||
content_path = strjoin(basedir, DATA_DIR, NULL);
|
||||
free(basedir);
|
||||
#else
|
||||
content_path = FILE_PREFIX;
|
||||
#endif
|
||||
|
||||
conf_path = SDL_GetPrefPath("", "taisei");
|
||||
scr_path = strjoin(conf_path, SCR_DIR, NULL);
|
||||
rpy_path = strjoin(conf_path, RPY_DIR, NULL);
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
/*
|
||||
* This software is licensed under the terms of the MIT-License
|
||||
* See COPYING for further information.
|
||||
* ---
|
||||
* Copyright (C) 2011, Lukas Weber <laochailan@web.de>
|
||||
*/
|
||||
|
||||
#include "native.h"
|
||||
#include "global.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define CFG_DIR "/.taisei"
|
||||
#define SCR_DIR "/screenshots"
|
||||
#define RPY_DIR "/replays"
|
||||
|
||||
char *conf_path;
|
||||
char *scr_path;
|
||||
char *rpy_path;
|
||||
|
||||
const char *get_prefix(void) {
|
||||
return FILE_PREFIX;
|
||||
}
|
||||
|
||||
const char *get_config_path(void) {
|
||||
return conf_path;
|
||||
}
|
||||
|
||||
const char *get_screenshots_path(void) {
|
||||
return scr_path;
|
||||
}
|
||||
|
||||
const char *get_replays_path(void) {
|
||||
return rpy_path;
|
||||
}
|
||||
|
||||
void init_paths(void) {
|
||||
conf_path = strjoin(getenv("HOME"), CFG_DIR, NULL);
|
||||
scr_path = strjoin(conf_path, SCR_DIR, NULL);
|
||||
rpy_path = strjoin(conf_path, RPY_DIR, NULL);
|
||||
}
|
|
@ -8,7 +8,6 @@
|
|||
#include <zlib.h>
|
||||
|
||||
#include "progress.h"
|
||||
#include "paths/native.h"
|
||||
#include "stage.h"
|
||||
|
||||
/*
|
||||
|
@ -68,10 +67,6 @@ static uint8_t progress_magic_bytes[] = {
|
|||
0x00, 0x67, 0x74, 0x66, 0x6f, 0xe3, 0x83, 0x84
|
||||
};
|
||||
|
||||
static char* progress_getpath(void) {
|
||||
return strfmt("%s/%s", get_config_path(), PROGRESS_FILENAME);
|
||||
}
|
||||
|
||||
static uint32_t progress_checksum(uint8_t *buf, size_t num) {
|
||||
return crc32(0xB16B00B5, buf, num);
|
||||
}
|
||||
|
@ -573,31 +568,25 @@ void progress_load(void) {
|
|||
progress_save();
|
||||
#endif
|
||||
|
||||
char *p = progress_getpath();
|
||||
SDL_RWops *file = SDL_RWFromFile(p, "rb");
|
||||
SDL_RWops *file = vfs_open(PROGRESS_FILE, VFS_MODE_READ);
|
||||
|
||||
if(!file) {
|
||||
log_warn("Couldn't open the progress file: %s", SDL_GetError());
|
||||
free(p);
|
||||
log_warn("Couldn't open the progress file: %s", vfs_get_error());
|
||||
return;
|
||||
}
|
||||
|
||||
free(p);
|
||||
progress_read(file);
|
||||
SDL_RWclose(file);
|
||||
}
|
||||
|
||||
void progress_save(void) {
|
||||
char *p = progress_getpath();
|
||||
SDL_RWops *file = SDL_RWFromFile(p, "wb");
|
||||
SDL_RWops *file = vfs_open(PROGRESS_FILE, VFS_MODE_WRITE);
|
||||
|
||||
if(!file) {
|
||||
log_warn("Couldn't open the progress file: %s", SDL_GetError());
|
||||
free(p);
|
||||
log_warn("Couldn't open the progress file: %s", vfs_get_error());
|
||||
return;
|
||||
}
|
||||
|
||||
free(p);
|
||||
progress_write(file);
|
||||
SDL_RWclose(file);
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
#include <SDL.h>
|
||||
#include "ending.h"
|
||||
|
||||
#define PROGRESS_FILENAME "progress.dat"
|
||||
#define PROGRESS_FILE "storage/progress.dat"
|
||||
#define PROGRESS_MAXFILESIZE 4096
|
||||
|
||||
#ifdef DEBUG
|
||||
|
|
88
src/replay.c
88
src/replay.c
|
@ -14,7 +14,6 @@
|
|||
#include <time.h>
|
||||
|
||||
#include "global.h"
|
||||
#include "paths/native.h"
|
||||
|
||||
static uint8_t replay_magic_header[] = REPLAY_MAGIC_HEADER;
|
||||
|
||||
|
@ -137,7 +136,7 @@ static void replay_write_string(SDL_RWops *file, char *str) {
|
|||
SDL_RWwrite(file, str, 1, strlen(str));
|
||||
}
|
||||
|
||||
static int replay_write_stage_event(ReplayEvent *evt, SDL_RWops *file) {
|
||||
static bool replay_write_stage_event(ReplayEvent *evt, SDL_RWops *file) {
|
||||
SDL_WriteLE32(file, evt->frame);
|
||||
SDL_WriteU8(file, evt->type);
|
||||
SDL_WriteLE16(file, evt->value);
|
||||
|
@ -166,7 +165,7 @@ static uint32_t replay_calc_stageinfo_checksum(ReplayStage *stg) {
|
|||
return cs;
|
||||
}
|
||||
|
||||
static int replay_write_stage(ReplayStage *stg, SDL_RWops *file) {
|
||||
static bool replay_write_stage(ReplayStage *stg, SDL_RWops *file) {
|
||||
SDL_WriteLE16(file, stg->stage);
|
||||
SDL_WriteLE32(file, stg->seed);
|
||||
SDL_WriteU8(file, stg->diff);
|
||||
|
@ -188,7 +187,7 @@ static int replay_write_stage(ReplayStage *stg, SDL_RWops *file) {
|
|||
return true;
|
||||
}
|
||||
|
||||
int replay_write(Replay *rpy, SDL_RWops *file, bool compression) {
|
||||
bool replay_write(Replay *rpy, SDL_RWops *file, bool compression) {
|
||||
uint16_t version = REPLAY_STRUCT_VERSION;
|
||||
int i, j;
|
||||
|
||||
|
@ -271,7 +270,7 @@ static void replay_read_string(SDL_RWops *file, char **ptr) {
|
|||
SDL_RWread(file, *ptr, 1, len);
|
||||
}
|
||||
|
||||
static int replay_read_header(Replay *rpy, SDL_RWops *file, int64_t filesize, size_t *ofs) {
|
||||
static bool replay_read_header(Replay *rpy, SDL_RWops *file, int64_t filesize, size_t *ofs) {
|
||||
uint8_t header[sizeof(replay_magic_header)];
|
||||
(*ofs) += sizeof(header);
|
||||
|
||||
|
@ -298,7 +297,7 @@ static int replay_read_header(Replay *rpy, SDL_RWops *file, int64_t filesize, si
|
|||
return true;
|
||||
}
|
||||
|
||||
static int replay_read_meta(Replay *rpy, SDL_RWops *file, int64_t filesize) {
|
||||
static bool replay_read_meta(Replay *rpy, SDL_RWops *file, int64_t filesize) {
|
||||
replay_read_string(file, &rpy->playername);
|
||||
PRINTPROP(rpy->playername, s);
|
||||
|
||||
|
@ -341,7 +340,7 @@ static int replay_read_meta(Replay *rpy, SDL_RWops *file, int64_t filesize) {
|
|||
return true;
|
||||
}
|
||||
|
||||
static int replay_read_events(Replay *rpy, SDL_RWops *file, int64_t filesize) {
|
||||
static bool replay_read_events(Replay *rpy, SDL_RWops *file, int64_t filesize) {
|
||||
for(int i = 0; i < rpy->numstages; ++i) {
|
||||
ReplayStage *stg = rpy->stages + i;
|
||||
|
||||
|
@ -365,8 +364,8 @@ static int replay_read_events(Replay *rpy, SDL_RWops *file, int64_t filesize) {
|
|||
return true;
|
||||
}
|
||||
|
||||
int replay_read(Replay *rpy, SDL_RWops *file, ReplayReadMode mode) {
|
||||
int64_t filesize; // must be signed
|
||||
bool replay_read(Replay *rpy, SDL_RWops *file, ReplayReadMode mode) {
|
||||
ssize_t filesize; // must be signed
|
||||
SDL_RWops *vfile = file;
|
||||
|
||||
mode &= REPLAY_READ_ALL;
|
||||
|
@ -478,58 +477,73 @@ int replay_read(Replay *rpy, SDL_RWops *file, ReplayReadMode mode) {
|
|||
#undef CHECKPROP
|
||||
#undef PRINTPROP
|
||||
|
||||
char* replay_getpath(const char *name, bool ext) {
|
||||
return ext ? strfmt("%s/%s.%s", get_replays_path(), name, REPLAY_EXTENSION) :
|
||||
strfmt("%s/%s", get_replays_path(), name);
|
||||
static char* replay_getpath(const char *name, bool ext) {
|
||||
return ext ? strfmt("storage/replays/%s.%s", name, REPLAY_EXTENSION) :
|
||||
strfmt("storage/replays/%s", name);
|
||||
}
|
||||
|
||||
int replay_save(Replay *rpy, const char *name) {
|
||||
bool replay_save(Replay *rpy, const char *name) {
|
||||
char *p = replay_getpath(name, !strendswith(name, REPLAY_EXTENSION));
|
||||
log_info("Saving %s", p);
|
||||
char *sp = vfs_syspath_or_repr(p);
|
||||
log_info("Saving %s", sp);
|
||||
free(sp);
|
||||
|
||||
SDL_RWops *file = SDL_RWFromFile(p, "wb");
|
||||
SDL_RWops *file = vfs_open(p, VFS_MODE_WRITE);
|
||||
free(p);
|
||||
|
||||
if(!file) {
|
||||
log_warn("SDL_RWFromFile() failed: %s", SDL_GetError());
|
||||
log_warn("VFS error: %s", vfs_get_error());
|
||||
return false;
|
||||
}
|
||||
|
||||
int result = replay_write(rpy, file, REPLAY_WRITE_COMPRESSED);
|
||||
bool result = replay_write(rpy, file, REPLAY_WRITE_COMPRESSED);
|
||||
SDL_RWclose(file);
|
||||
return result;
|
||||
}
|
||||
|
||||
int replay_load(Replay *rpy, const char *name, ReplayReadMode mode) {
|
||||
char *p;
|
||||
bool replay_load(Replay *rpy, const char *name, ReplayReadMode mode) {
|
||||
char *p = replay_getpath(name, !strendswith(name, REPLAY_EXTENSION));
|
||||
char *sp = vfs_syspath_or_repr(p);
|
||||
log_info("Loading %s (mode %i)", sp, mode);
|
||||
free(sp);
|
||||
|
||||
if(mode & REPLAY_READ_RAWPATH) {
|
||||
p = (char*)name;
|
||||
} else {
|
||||
p = replay_getpath(name, !strendswith(name, REPLAY_EXTENSION));
|
||||
SDL_RWops *file = vfs_open(p, VFS_MODE_READ);
|
||||
free(p);
|
||||
|
||||
if(!file) {
|
||||
log_warn("VFS error: %s", vfs_get_error());
|
||||
return false;
|
||||
}
|
||||
|
||||
log_info("replay_load(): loading %s (mode %i)", p, mode);
|
||||
bool result = replay_read(rpy, file, mode);
|
||||
|
||||
if(!result) {
|
||||
replay_destroy(rpy);
|
||||
}
|
||||
|
||||
SDL_RWclose(file);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool replay_load_syspath(Replay *rpy, const char *path, ReplayReadMode mode) {
|
||||
log_info("Loading %s (mode %i)", path, mode);
|
||||
SDL_RWops *file;
|
||||
|
||||
#ifndef __WINDOWS__
|
||||
if(!strcmp(name,"-"))
|
||||
if(!strcmp(path, "-"))
|
||||
file = SDL_RWFromFP(stdin,false);
|
||||
else
|
||||
file = SDL_RWFromFile(p, "rb");
|
||||
file = SDL_RWFromFile(path, "rb");
|
||||
#else
|
||||
file = SDL_RWFromFile(p, "rb");
|
||||
file = SDL_RWFromFile(path, "rb");
|
||||
#endif
|
||||
|
||||
if(!(mode & REPLAY_READ_RAWPATH)) {
|
||||
free(p);
|
||||
}
|
||||
|
||||
if(!file) {
|
||||
log_warn("SDL_RWFromFile() failed: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
int result = replay_read(rpy, file, mode);
|
||||
bool result = replay_read(rpy, file, mode);
|
||||
|
||||
if(!result) {
|
||||
replay_destroy(rpy);
|
||||
|
@ -690,13 +704,3 @@ void replay_play(Replay *rpy, int firstidx) {
|
|||
global.replay_stage = NULL;
|
||||
free_resources(false);
|
||||
}
|
||||
|
||||
void replay_play_path(const char *path, int firstidx) {
|
||||
replay_destroy(&global.replay);
|
||||
|
||||
if(!replay_load(&global.replay, path, REPLAY_READ_ALL | REPLAY_READ_RAWPATH)) {
|
||||
return;
|
||||
}
|
||||
|
||||
replay_play(&global.replay, firstidx);
|
||||
}
|
||||
|
|
13
src/replay.h
13
src/replay.h
|
@ -139,7 +139,6 @@ typedef enum ReplayReadMode {
|
|||
REPLAY_READ_META = 1,
|
||||
REPLAY_READ_EVENTS = 2,
|
||||
REPLAY_READ_ALL = 3, // includes the other two
|
||||
REPLAY_READ_RAWPATH = 4, // only used by replay_load()
|
||||
} ReplayReadMode;
|
||||
|
||||
void replay_init(Replay *rpy);
|
||||
|
@ -152,18 +151,16 @@ void replay_stage_event(ReplayStage *stg, uint32_t frame, uint8_t type, int16_t
|
|||
void replay_stage_check_desync(ReplayStage *stg, int time, uint16_t check, ReplayMode mode);
|
||||
void replay_stage_sync_player_state(ReplayStage *stg, Player *plr);
|
||||
|
||||
int replay_write(Replay *rpy, SDL_RWops *file, bool compression);
|
||||
int replay_read(Replay *rpy, SDL_RWops *file, ReplayReadMode mode);
|
||||
bool replay_write(Replay *rpy, SDL_RWops *file, bool compression);
|
||||
bool replay_read(Replay *rpy, SDL_RWops *file, ReplayReadMode mode);
|
||||
|
||||
int replay_save(Replay *rpy, const char *name);
|
||||
int replay_load(Replay *rpy, const char *name, ReplayReadMode mode);
|
||||
bool replay_save(Replay *rpy, const char *name);
|
||||
bool replay_load(Replay *rpy, const char *name, ReplayReadMode mode);
|
||||
bool replay_load_syspath(Replay *rpy, const char *path, ReplayReadMode mode);
|
||||
|
||||
void replay_copy(Replay *dst, Replay *src, bool steal_events);
|
||||
|
||||
char* replay_getpath(const char *name, bool ext); // must be freed
|
||||
|
||||
void replay_play(Replay *rpy, int firstidx);
|
||||
void replay_play_path(const char *path, int firstidx);
|
||||
|
||||
int replay_find_stage_idx(Replay *rpy, uint8_t stageid);
|
||||
|
||||
|
|
|
@ -21,6 +21,6 @@ void* load_music_begin(const char *path, unsigned int flags);
|
|||
void* load_music_end(void *opaque, const char *path, unsigned int flags);
|
||||
void unload_music(void *snd);
|
||||
|
||||
#define BGM_PATH_PREFIX "bgm/"
|
||||
#define BGM_PATH_PREFIX "res/bgm/"
|
||||
|
||||
#endif
|
||||
|
|
|
@ -23,7 +23,14 @@ bool check_music_path(const char *path) {
|
|||
}
|
||||
|
||||
void* load_music_begin(const char *path, unsigned int flags) {
|
||||
Mix_Music *music = Mix_LoadMUS(path);
|
||||
SDL_RWops *rwops = vfs_open(path, VFS_MODE_READ);
|
||||
|
||||
if(!rwops) {
|
||||
log_warn("VFS error: %s", vfs_get_error());
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Mix_Music *music = Mix_LoadMUS_RW(rwops, true);
|
||||
|
||||
if(!music) {
|
||||
log_warn("Mix_LoadMUS() failed: %s", Mix_GetError());
|
||||
|
|
|
@ -7,25 +7,30 @@
|
|||
|
||||
#include "font.h"
|
||||
#include "global.h"
|
||||
#include "paths/native.h"
|
||||
|
||||
struct Fonts _fonts;
|
||||
|
||||
TTF_Font* load_font(char *name, int size) {
|
||||
char *buf = strjoin(get_prefix(), name, NULL);
|
||||
TTF_Font* load_font(char *vfspath, int size) {
|
||||
char *syspath = vfs_syspath_or_repr(vfspath);
|
||||
|
||||
SDL_RWops *rwops = vfs_open(vfspath, VFS_MODE_READ);
|
||||
|
||||
if(!rwops) {
|
||||
log_fatal("VFS error: %s", vfs_get_error());
|
||||
}
|
||||
|
||||
// XXX: what would be the best rounding strategy here?
|
||||
size = rint(size * resources.fontren.quality);
|
||||
|
||||
TTF_Font *f = TTF_OpenFont(buf, size);
|
||||
TTF_Font *f = TTF_OpenFontRW(rwops, true, size);
|
||||
|
||||
if(!f) {
|
||||
log_fatal("Failed to load font '%s' @ %i: %s", buf, size, TTF_GetError());
|
||||
log_fatal("Failed to load font '%s' @ %i: %s", syspath, size, TTF_GetError());
|
||||
}
|
||||
|
||||
log_info("Loaded '%s' @ %i", buf, size);
|
||||
log_info("Loaded '%s' @ %i", syspath, size);
|
||||
|
||||
free(buf);
|
||||
free(syspath);
|
||||
return f;
|
||||
}
|
||||
|
||||
|
@ -125,9 +130,9 @@ void uninit_fonts(void) {
|
|||
|
||||
void load_fonts(float quality) {
|
||||
fontrenderer_init(&resources.fontren, quality);
|
||||
_fonts.standard = load_font("gfx/LinBiolinum.ttf", 20);
|
||||
_fonts.mainmenu = load_font("gfx/immortal.ttf", 35);
|
||||
_fonts.small = load_font("gfx/LinBiolinum.ttf", 14);
|
||||
_fonts.standard = load_font("res/gfx/LinBiolinum.ttf", 20);
|
||||
_fonts.mainmenu = load_font("res/gfx/immortal.ttf", 35);
|
||||
_fonts.small = load_font("res/gfx/LinBiolinum.ttf", 14);
|
||||
}
|
||||
|
||||
void reload_fonts(float quality) {
|
||||
|
|
|
@ -131,10 +131,10 @@ static void free_obj(ObjFileData *data) {
|
|||
}
|
||||
|
||||
static void parse_obj(const char *filename, ObjFileData *data) {
|
||||
SDL_RWops *rw = SDL_RWFromFile(filename, "r");
|
||||
SDL_RWops *rw = vfs_open(filename, VFS_MODE_READ);
|
||||
|
||||
if(!rw) {
|
||||
log_warn("SDL_RWFromFile() failed: %s", SDL_GetError());
|
||||
log_warn("VFS error: %s", vfs_get_error());
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ Model* get_model(const char *name);
|
|||
void draw_model_p(Model *model);
|
||||
void draw_model(const char *name);
|
||||
|
||||
#define MDL_PATH_PREFIX "models/"
|
||||
#define MDL_PATH_PREFIX "res/models/"
|
||||
#define MDL_EXTENSION ".obj"
|
||||
|
||||
#endif
|
||||
|
|
|
@ -6,9 +6,6 @@
|
|||
*/
|
||||
|
||||
#include "resource.h"
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include "paths/native.h"
|
||||
#include "config.h"
|
||||
#include "video.h"
|
||||
#include "menu/mainmenu.h"
|
||||
|
@ -274,7 +271,9 @@ static Resource* load_resource_finish(void *opaque, ResourceHandler *handler, co
|
|||
return NULL;
|
||||
}
|
||||
|
||||
Resource *res = insert_resource(handler->type, name, raw, flags, path);
|
||||
char *sp = vfs_syspath_or_repr(path);
|
||||
Resource *res = insert_resource(handler->type, name, raw, flags, sp);
|
||||
free(sp);
|
||||
|
||||
free(allocated_path);
|
||||
free(allocated_name);
|
||||
|
@ -387,11 +386,11 @@ const char* resource_util_filename(const char *path) {
|
|||
|
||||
void load_resources(void) {
|
||||
if(glext.draw_instanced) {
|
||||
load_shader_snippets("shader/laser_snippets", "laser_", RESF_PERMANENT);
|
||||
load_shader_snippets(SHA_PATH_PREFIX "laser_snippets", "laser_", RESF_PERMANENT);
|
||||
}
|
||||
|
||||
menu_preload();
|
||||
resources.stage_postprocess = postprocess_load("shader/postprocess.conf");
|
||||
resources.stage_postprocess = postprocess_load(SHA_PATH_PREFIX "postprocess.conf");
|
||||
}
|
||||
|
||||
void free_resources(bool all) {
|
||||
|
@ -408,9 +407,9 @@ void free_resources(bool all) {
|
|||
if(!all && res->flags & RESF_PERMANENT)
|
||||
continue;
|
||||
|
||||
ResourceFlags flags = res->flags;
|
||||
ResourceFlags flags __attribute__((unused)) = res->flags;
|
||||
unload_resource(res);
|
||||
log_info("Unloaded %s '%s' (%s)", resource_type_names[type], name,
|
||||
log_debug("Unloaded %s '%s' (%s)", resource_type_names[type], name,
|
||||
(flags & RESF_PERMANENT) ? "permanent" : "transient"
|
||||
);
|
||||
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
#include "model.h"
|
||||
#include "postprocess.h"
|
||||
#include "hashtable.h"
|
||||
#include "paths/native.h"
|
||||
|
||||
typedef enum ResourceType {
|
||||
RES_TEXTURE,
|
||||
|
@ -39,21 +38,18 @@ typedef enum ResourceFlags {
|
|||
|
||||
#define RESF_DEFAULT 0
|
||||
|
||||
// All paths are relative to the current working directory, which can assumed to be the resources directory,
|
||||
// unless mentioned otherwise.
|
||||
|
||||
// Converts a path into an abstract resource name to be used as the hashtable key.
|
||||
// Converts a vfs path into an abstract resource name to be used as the hashtable key.
|
||||
// This method is optional, the default strategy is to take the path minus the prefix and extension.
|
||||
// The returned name must be free()'d.
|
||||
typedef char* (*ResourceNameFunc)(const char *path);
|
||||
|
||||
// Converts an abstract resource name into a path from which a resource with that name could be loaded.
|
||||
// Converts an abstract resource name into a vfs path from which a resource with that name could be loaded.
|
||||
// The path may not actually exist or be usable. The load function (see below) shall deal with such cases.
|
||||
// The returned path must be free()'d.
|
||||
// May return NULL on failure, but does not have to.
|
||||
typedef char* (*ResourceFindFunc)(const char *name);
|
||||
|
||||
// Tells whether the resource handler should attempt to load a file, specified by a path.
|
||||
// Tells whether the resource handler should attempt to load a file, specified by a vfs path.
|
||||
typedef bool (*ResourceCheckFunc)(const char *path);
|
||||
|
||||
// Begins loading a resource specified by path.
|
||||
|
|
|
@ -21,6 +21,6 @@ void* load_sound_begin(const char *path, unsigned int flags);
|
|||
void* load_sound_end(void *opaque, const char *path, unsigned int flags);
|
||||
void unload_sound(void *snd);
|
||||
|
||||
#define SFX_PATH_PREFIX "sfx/"
|
||||
#define SFX_PATH_PREFIX "res/sfx/"
|
||||
|
||||
#endif
|
||||
|
|
|
@ -23,7 +23,14 @@ bool check_sound_path(const char *path) {
|
|||
}
|
||||
|
||||
void* load_sound_begin(const char *path, unsigned int flags) {
|
||||
Mix_Chunk *sound = Mix_LoadWAV(path);
|
||||
SDL_RWops *rwops = vfs_open(path, VFS_MODE_READ);
|
||||
|
||||
if(!rwops) {
|
||||
log_warn("VFS error: %s", vfs_get_error());
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Mix_Chunk *sound = Mix_LoadWAV_RW(rwops, true);
|
||||
|
||||
if(!sound) {
|
||||
log_warn("Mix_LoadWAV() failed: %s", Mix_GetError());
|
||||
|
|
|
@ -30,7 +30,7 @@ Shader* get_shader_optional(const char *name);
|
|||
|
||||
int uniloc(Shader *sha, const char *name);
|
||||
|
||||
#define SHA_PATH_PREFIX "shader/"
|
||||
#define SHA_PATH_PREFIX "res/shader/"
|
||||
#define SHA_EXTENSION ".sha"
|
||||
|
||||
#define SHA_DELIM "%% -- FRAG"
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
#include "texture.h"
|
||||
#include "resource.h"
|
||||
#include "global.h"
|
||||
#include "paths/native.h"
|
||||
#include "vbo.h"
|
||||
|
||||
char* texture_path(const char *name) {
|
||||
|
@ -86,10 +85,6 @@ Texture* prefix_get_tex(const char *name, const char *prefix) {
|
|||
|
||||
static ImageData* load_png_p(const char *filename, SDL_RWops *rwops) {
|
||||
#define PNGFAIL(msg) { log_warn("Couldn't load '%s': %s", filename, msg); return NULL; }
|
||||
if(!rwops) {
|
||||
PNGFAIL(SDL_GetError())
|
||||
}
|
||||
|
||||
png_structp png_ptr;
|
||||
if(!(png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL))) {
|
||||
PNGFAIL("png_create_read_struct() failed")
|
||||
|
@ -150,13 +145,16 @@ static ImageData* load_png_p(const char *filename, SDL_RWops *rwops) {
|
|||
}
|
||||
|
||||
static ImageData* load_png(const char *filename) {
|
||||
SDL_RWops *rwops = SDL_RWFromFile(filename, "r");
|
||||
ImageData *img = load_png_p(filename, rwops);
|
||||
SDL_RWops *rwops = vfs_open(filename, VFS_MODE_READ);
|
||||
|
||||
if(rwops) {
|
||||
SDL_RWclose(rwops);
|
||||
if(!rwops) {
|
||||
log_warn("VFS error: %s", vfs_get_error());
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ImageData *img = load_png_p(filename, rwops);
|
||||
|
||||
SDL_RWclose(rwops);
|
||||
return img;
|
||||
}
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ void loop_tex_line(complex a, complex b, float w, float t, const char *texture);
|
|||
Texture* get_tex(const char *name);
|
||||
Texture* prefix_get_tex(const char *name, const char *prefix);
|
||||
|
||||
#define TEX_PATH_PREFIX "gfx/"
|
||||
#define TEX_PATH_PREFIX "res/gfx/"
|
||||
#define TEX_EXTENSION ".png"
|
||||
|
||||
#endif
|
||||
|
|
18
src/util.c
18
src/util.c
|
@ -129,6 +129,16 @@ void strip_trailing_slashes(char *buf) {
|
|||
*c = 0;
|
||||
}
|
||||
|
||||
char* strappend(char **dst, char *src) {
|
||||
if(!*dst) {
|
||||
return *dst = strdup(src);
|
||||
}
|
||||
|
||||
*dst = realloc(*dst, strlen(*dst) + strlen(src) + 1);
|
||||
strcat(*dst, src);
|
||||
return *dst;
|
||||
}
|
||||
|
||||
/*
|
||||
* public domain strtok_r() by Charlie Gordon
|
||||
*
|
||||
|
@ -310,10 +320,10 @@ char* read_all(const char *filename, int *outsize) {
|
|||
char *text;
|
||||
size_t size;
|
||||
|
||||
SDL_RWops *file = SDL_RWFromFile(filename, "r");
|
||||
SDL_RWops *file = vfs_open(filename, VFS_MODE_READ);
|
||||
|
||||
if(!file) {
|
||||
log_warn("SDL_RWFromFile() failed: %s", SDL_GetError());
|
||||
log_warn("VFS error: %s", vfs_get_error());
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -388,10 +398,10 @@ bool parse_keyvalue_stream_cb(SDL_RWops *strm, KVCallback callback, void *data)
|
|||
}
|
||||
|
||||
bool parse_keyvalue_file_cb(const char *filename, KVCallback callback, void *data) {
|
||||
SDL_RWops *strm = SDL_RWFromFile(filename, "r");
|
||||
SDL_RWops *strm = vfs_open(filename, VFS_MODE_READ);
|
||||
|
||||
if(!strm) {
|
||||
log_warn("SDL_RWFromFile() failed: %s", SDL_GetError());
|
||||
log_warn("VFS error: %s", vfs_get_error());
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
10
src/util.h
10
src/util.h
|
@ -11,6 +11,7 @@
|
|||
#include <SDL.h>
|
||||
#include "log.h"
|
||||
#include "hashtable.h"
|
||||
#include "vfs/public.h"
|
||||
|
||||
//
|
||||
// compatibility
|
||||
|
@ -39,6 +40,7 @@ char* vstrfmt(const char *fmt, va_list args);
|
|||
char* strfmt(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
|
||||
void strip_trailing_slashes(char *buf);
|
||||
char* strtok_r(char *str, const char *delim, char **nextp);
|
||||
char* strappend(char **dst, char *src);
|
||||
#undef strdup
|
||||
#define strdup SDL_strdup
|
||||
|
||||
|
@ -135,19 +137,13 @@ noreturn void _ts_assert_fail(const char *cond, const char *func, const char *fi
|
|||
#define assert(cond) _assert(cond, true)
|
||||
#define assert_nolog(cond) _assert(cond, false)
|
||||
|
||||
#ifndef __POSIX__
|
||||
#define MKDIR(p) mkdir(p)
|
||||
#else
|
||||
#define MKDIR(p) mkdir(p, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)
|
||||
#endif
|
||||
|
||||
//
|
||||
// safeguards against some dangerous or otherwise undesirable practices
|
||||
//
|
||||
|
||||
#undef fopen
|
||||
FILE* fopen() __attribute__((deprecated(
|
||||
"Use SDL_RWFromFile instead")));
|
||||
"Use vfs_open or SDL_RWFromFile instead")));
|
||||
|
||||
#undef strncat
|
||||
char* strncat() __attribute__((deprecated(
|
||||
|
|
95
src/vfs/pathutil.c
Normal file
95
src/vfs/pathutil.c
Normal file
|
@ -0,0 +1,95 @@
|
|||
|
||||
#include "private.h"
|
||||
|
||||
char* vfs_path_normalize(const char *path, char *out) {
|
||||
const char *p = path;
|
||||
char *o = out;
|
||||
char *last_sep = out - 1;
|
||||
char *path_end = strchr(path, 0);
|
||||
|
||||
while(p < path_end) {
|
||||
if(strchr(VFS_PATH_SEPS, *p)) {
|
||||
if(o > out && *(o - 1) != VFS_PATH_SEP) {
|
||||
last_sep = o;
|
||||
*o++ = VFS_PATH_SEP;
|
||||
}
|
||||
|
||||
do {
|
||||
++p;
|
||||
} while(strchr(VFS_PATH_SEPS, *p));
|
||||
} else if(*p == '.' && strchr(VFS_PATH_SEPS, *(p + 1))) {
|
||||
p += 2;
|
||||
} else if(!strncmp(p, "..", 2) && strchr(VFS_PATH_SEPS, *(p + 2))) {
|
||||
if(last_sep >= out) {
|
||||
do {
|
||||
--last_sep;
|
||||
} while(*last_sep != VFS_PATH_SEP && last_sep >= out);
|
||||
|
||||
o = last_sep-- + 1;
|
||||
}
|
||||
|
||||
p += 3;
|
||||
} else {
|
||||
*o++ = *p++;
|
||||
}
|
||||
}
|
||||
|
||||
*o = 0;
|
||||
|
||||
// log_debug("[%s] --> [%s]", path, out);
|
||||
return out;
|
||||
}
|
||||
|
||||
char* vfs_path_normalize_alloc(const char *path) {
|
||||
return vfs_path_normalize(path, strdup(path));
|
||||
}
|
||||
|
||||
char* vfs_normalize_path_inplace(char *path) {
|
||||
char buf[strlen(path)+1];
|
||||
strcpy(buf, path);
|
||||
vfs_path_normalize(path, buf);
|
||||
strcpy(path, buf);
|
||||
return path;
|
||||
}
|
||||
|
||||
void vfs_path_split_left(char *path, char **lpath, char **rpath) {
|
||||
char *sep;
|
||||
|
||||
while(*path == VFS_PATH_SEP)
|
||||
++path;
|
||||
|
||||
*lpath = path;
|
||||
|
||||
if(sep = strchr(path, VFS_PATH_SEP)) {
|
||||
*sep = 0;
|
||||
*rpath = sep + 1;
|
||||
} else {
|
||||
*rpath = path + strlen(path);
|
||||
}
|
||||
}
|
||||
|
||||
void vfs_path_split_right(char *path, char **lpath, char **rpath) {
|
||||
char *sep, *c;
|
||||
assert(*path != 0);
|
||||
|
||||
while(*(c = strrchr(path, 0) - 1) == VFS_PATH_SEP)
|
||||
*c = 0;
|
||||
|
||||
if(sep = strrchr(path, VFS_PATH_SEP)) {
|
||||
*sep = 0;
|
||||
*lpath = path;
|
||||
*rpath = sep + 1;
|
||||
} else {
|
||||
*lpath = path + strlen(path);
|
||||
*rpath = path;
|
||||
}
|
||||
}
|
||||
|
||||
void vfs_path_root_prefix(char *path) {
|
||||
if(!strchr(VFS_PATH_SEPS, *path)) {
|
||||
memmove(path+1, path, strlen(path)+1);
|
||||
*path = VFS_PATH_SEP;
|
||||
} else {
|
||||
*path = VFS_PATH_SEP;
|
||||
}
|
||||
}
|
22
src/vfs/pathutil.h
Normal file
22
src/vfs/pathutil.h
Normal file
|
@ -0,0 +1,22 @@
|
|||
|
||||
#ifndef TAISEI_VFS_PATHUTIL
|
||||
#define TAISEI_VFS_PATHUTIL
|
||||
|
||||
#include <SDL_platform.h>
|
||||
|
||||
#ifdef __WINDOWS__
|
||||
#define VFS_PATH_SEPS "/\\"
|
||||
#else
|
||||
#define VFS_PATH_SEPS "/"
|
||||
#endif
|
||||
|
||||
#define VFS_PATH_SEP VFS_PATH_SEPS[0]
|
||||
|
||||
char* vfs_path_normalize(const char *path, char *out);
|
||||
char* vfs_path_normalize_alloc(const char *path);
|
||||
char* vfs_path_normalize_inplace(char *path);
|
||||
void vfs_path_split_left(char *path, char **lpath, char **rpath);
|
||||
void vfs_path_split_right(char *path, char **lpath, char **rpath);
|
||||
void vfs_path_root_prefix(char *path);
|
||||
|
||||
#endif
|
228
src/vfs/private.c
Normal file
228
src/vfs/private.c
Normal file
|
@ -0,0 +1,228 @@
|
|||
|
||||
#include "private.h"
|
||||
#include "vdir.h"
|
||||
|
||||
VFSNode *vfs_root;
|
||||
|
||||
typedef struct vfs_tls_s {
|
||||
char *error_str;
|
||||
} vfs_tls_t;
|
||||
|
||||
static SDL_TLSID vfs_tls_id;
|
||||
static vfs_tls_t *vfs_tls_fallback;
|
||||
|
||||
static void vfs_tls_free(void *vtls) {
|
||||
if(vtls) {
|
||||
vfs_tls_t *tls = vtls;
|
||||
free(tls->error_str);
|
||||
free(tls);
|
||||
}
|
||||
}
|
||||
|
||||
static vfs_tls_t* vfs_tls_get(void) {
|
||||
if(vfs_tls_id) {
|
||||
return SDL_TLSGet(vfs_tls_id);
|
||||
}
|
||||
|
||||
return vfs_tls_fallback;
|
||||
}
|
||||
|
||||
void vfs_init(void) {
|
||||
vfs_root = vfs_alloc();
|
||||
vfs_vdir_init(vfs_root);
|
||||
vfs_root->name = strdup("");
|
||||
|
||||
vfs_tls_id = SDL_TLSCreate();
|
||||
|
||||
if(vfs_tls_id) {
|
||||
SDL_TLSSet(vfs_tls_id, calloc(1, sizeof(vfs_tls_t)), vfs_tls_free);
|
||||
vfs_tls_fallback = NULL;
|
||||
} else {
|
||||
log_warn("SDL_TLSCreate(): failed: %s", SDL_GetError());
|
||||
vfs_tls_fallback = calloc(1, sizeof(vfs_tls_t));
|
||||
}
|
||||
}
|
||||
|
||||
void vfs_uninit(void) {
|
||||
vfs_free(vfs_root);
|
||||
vfs_tls_free(vfs_tls_fallback);
|
||||
|
||||
vfs_root = NULL;
|
||||
vfs_tls_id = 0;
|
||||
vfs_tls_fallback = NULL;
|
||||
}
|
||||
|
||||
VFSNode* vfs_alloc(void) {
|
||||
return calloc(1, sizeof(VFSNode));
|
||||
}
|
||||
|
||||
void vfs_free(VFSNode *node) {
|
||||
if(!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(node->funcs && node->funcs->free) {
|
||||
node->funcs->free(node);
|
||||
}
|
||||
|
||||
free(node->name);
|
||||
free(node);
|
||||
}
|
||||
|
||||
VFSInfo vfs_query_node(VFSNode *node) {
|
||||
assert(node != NULL);
|
||||
assert(node->funcs != NULL);
|
||||
assert(node->funcs->query != NULL);
|
||||
|
||||
return node->funcs->query(node);
|
||||
}
|
||||
|
||||
VFSNode* vfs_locate(VFSNode *root, const char *path) {
|
||||
assert(root != NULL);
|
||||
assert(path != NULL);
|
||||
assert(root->funcs != NULL);
|
||||
|
||||
#ifndef NDEBUG
|
||||
char buf[strlen(path)+1];
|
||||
strcpy(buf, path);
|
||||
assert(!strcmp(path, vfs_path_normalize(path, buf)));
|
||||
#endif
|
||||
|
||||
if(!*path) {
|
||||
return root;
|
||||
}
|
||||
|
||||
if(root->funcs->locate) {
|
||||
return root->funcs->locate(root, path);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void vfs_locate_cleanup(VFSNode *root, VFSNode *node) {
|
||||
if(node && !node->parent && node != root) {
|
||||
vfs_free(node);
|
||||
}
|
||||
}
|
||||
|
||||
bool vfs_mount(VFSNode *root, const char *mountpoint, VFSNode *subtree) {
|
||||
VFSNode *mpnode;
|
||||
char buf[2][strlen(mountpoint)+1];
|
||||
char *mpbase, *mpname;
|
||||
bool result = false;
|
||||
|
||||
mountpoint = vfs_path_normalize(mountpoint, buf[0]);
|
||||
strcpy(buf[1], buf[0]);
|
||||
vfs_path_split_right(buf[1], &mpbase, &mpname);
|
||||
|
||||
if(mpnode = vfs_locate(root, mountpoint)) {
|
||||
// mountpoint already exists - try to merge with the target node
|
||||
|
||||
if(mpnode->funcs->mount) {
|
||||
// expected to set error on failure
|
||||
result = mpnode->funcs->mount(mpnode, NULL, subtree);
|
||||
} else {
|
||||
result = false;
|
||||
vfs_set_error("Mountpoint '%s' already exists and does not support merging", mountpoint);
|
||||
}
|
||||
|
||||
vfs_locate_cleanup(root, mpnode);
|
||||
return result;
|
||||
}
|
||||
|
||||
if(mpnode = vfs_locate(root, mpbase)) {
|
||||
// try to become a subnode of parent (conventional mount)
|
||||
|
||||
if(mpnode->funcs->mount) {
|
||||
// expected to set error on failure
|
||||
result = mpnode->funcs->mount(mpnode, mpname, subtree);
|
||||
} else {
|
||||
result = false;
|
||||
vfs_set_error("Parent directory '%s' of mountpoint '%s' does not support mounting", mpbase, mountpoint);
|
||||
}
|
||||
|
||||
vfs_locate_cleanup(root, mpnode);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const char* vfs_iter(VFSNode *node, void **opaque) {
|
||||
if(node->funcs->iter) {
|
||||
return node->funcs->iter(node, opaque);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void vfs_iter_stop(VFSNode *node, void **opaque) {
|
||||
if(node->funcs->iter_stop) {
|
||||
node->funcs->iter_stop(node, opaque);
|
||||
}
|
||||
}
|
||||
|
||||
VFSNode* vfs_find_root(VFSNode *node) {
|
||||
VFSNode *root = node, *r = node;
|
||||
|
||||
while(r = r->parent)
|
||||
root = r;
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
char* vfs_repr(VFSNode *node) {
|
||||
assert(node != NULL);
|
||||
assert(node->funcs != NULL);
|
||||
assert(node->funcs->repr != NULL);
|
||||
|
||||
VFSInfo i = vfs_query_node(node);
|
||||
char *r, *o = node->funcs->repr(node);
|
||||
|
||||
r = strfmt("<%s (t:%i e:%i x:%i d:%i)>", o,
|
||||
node->type, i.error, i.exists, i.is_dir
|
||||
);
|
||||
|
||||
free(o);
|
||||
return r;
|
||||
}
|
||||
|
||||
void vfs_print_tree_recurse(SDL_RWops *dest, VFSNode *root, char *prefix) {
|
||||
void *o = NULL;
|
||||
char *newprefix = strfmt("%s%s%s", prefix, root->name, vfs_query_node(root).is_dir ? (char[]){VFS_PATH_SEP, 0} : "");
|
||||
char *r;
|
||||
|
||||
SDL_RWprintf(dest, "%s = %s\n", newprefix, r = vfs_repr(root));
|
||||
free(r);
|
||||
|
||||
for(const char *n; n = vfs_iter(root, &o);) {
|
||||
VFSNode *node = vfs_locate(root, n);
|
||||
if(node) {
|
||||
vfs_print_tree_recurse(dest, node, newprefix);
|
||||
vfs_locate_cleanup(root, node);
|
||||
}
|
||||
}
|
||||
|
||||
vfs_iter_stop(root, &o);
|
||||
free(newprefix);
|
||||
}
|
||||
|
||||
const char* vfs_get_error(void) {
|
||||
vfs_tls_t *tls = vfs_tls_get();
|
||||
return tls->error_str ? tls->error_str : "No error";
|
||||
}
|
||||
|
||||
void vfs_set_error(char *fmt, ...) {
|
||||
vfs_tls_t *tls = vfs_tls_get();
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
char *err = vstrfmt(fmt, args);
|
||||
free(tls->error_str);
|
||||
tls->error_str = err;
|
||||
va_end(args);
|
||||
|
||||
log_debug("%s", tls->error_str);
|
||||
}
|
||||
|
||||
void vfs_set_error_from_sdl(void) {
|
||||
vfs_set_error("SDL error: %s", SDL_GetError());
|
||||
}
|
92
src/vfs/private.h
Normal file
92
src/vfs/private.h
Normal file
|
@ -0,0 +1,92 @@
|
|||
|
||||
#ifndef TAISEI_VFS_PRIVATE
|
||||
#define TAISEI_VFS_PRIVATE
|
||||
|
||||
/*
|
||||
* This file should not be included by code outside of the vfs/ directory
|
||||
*/
|
||||
|
||||
#include "util.h"
|
||||
|
||||
#include "public.h"
|
||||
#include "pathutil.h"
|
||||
|
||||
typedef struct VFSNode VFSNode;
|
||||
|
||||
typedef enum VFSNodeType {
|
||||
VNODE_VDIR,
|
||||
VNODE_SYSPATH,
|
||||
VNODE_UNION,
|
||||
} VFSNodeType;
|
||||
|
||||
typedef char* (*VFSReprFunc)(VFSNode*);
|
||||
typedef void (*VFSFreeFunc)(VFSNode*);
|
||||
typedef VFSInfo (*VFSQueryFunc)(VFSNode*);
|
||||
typedef bool (*VFSMountFunc)(VFSNode *mountroot, const char *subname, VFSNode *mountee);
|
||||
typedef char* (*VFSSysPathFunc)(VFSNode*);
|
||||
|
||||
typedef VFSNode* (*VFSLocateFunc)(VFSNode *dirnode, const char* path);
|
||||
typedef const char* (*VFSIterFunc)(VFSNode *dirnode, void **opaque);
|
||||
typedef void (*VFSIterStopFunc)(VFSNode *dirnode, void **opaque);
|
||||
typedef bool (*VFSMkDirFunc)(VFSNode *parent, const char *subdir);
|
||||
|
||||
typedef SDL_RWops* (*VFSOpenFunc)(VFSNode *filenode, VFSOpenMode mode);
|
||||
|
||||
typedef struct VFSNodeFuncs {
|
||||
VFSReprFunc repr;
|
||||
VFSFreeFunc free;
|
||||
VFSQueryFunc query;
|
||||
VFSMountFunc mount;
|
||||
VFSSysPathFunc syspath;
|
||||
VFSLocateFunc locate;
|
||||
VFSIterFunc iter;
|
||||
VFSIterStopFunc iter_stop;
|
||||
VFSMkDirFunc mkdir;
|
||||
VFSOpenFunc open;
|
||||
} VFSNodeFuncs;
|
||||
|
||||
typedef struct VFSNode {
|
||||
VFSNodeType type;
|
||||
VFSNode *parent;
|
||||
VFSNodeFuncs *funcs;
|
||||
char *name;
|
||||
|
||||
union {
|
||||
// TODO: maybe somehow separate this
|
||||
|
||||
struct {
|
||||
Hashtable *contents;
|
||||
} vdir;
|
||||
|
||||
struct {
|
||||
char *path;
|
||||
} syspath;
|
||||
|
||||
struct {
|
||||
struct {
|
||||
ListContainer *all;
|
||||
VFSNode *primary;
|
||||
} members;
|
||||
} vunion;
|
||||
};
|
||||
} VFSNode;
|
||||
|
||||
extern VFSNode *vfs_root;
|
||||
|
||||
VFSNode* vfs_alloc(void);
|
||||
void vfs_free(VFSNode *node);
|
||||
char* vfs_repr(VFSNode *node);
|
||||
VFSNode* vfs_locate(VFSNode *root, const char *path);
|
||||
void vfs_locate_cleanup(VFSNode *root, VFSNode *node);
|
||||
VFSInfo vfs_query_node(VFSNode *node);
|
||||
bool vfs_mount(VFSNode *root, const char *mountpoint, VFSNode *subtree);
|
||||
const char* vfs_iter(VFSNode *node, void **opaque);
|
||||
void vfs_iter_stop(VFSNode *node, void **opaque);
|
||||
VFSNode* vfs_find_root(VFSNode *node);
|
||||
|
||||
void vfs_set_error(char *fmt, ...) __attribute__((format(printf, 1, 2)));
|
||||
void vfs_set_error_from_sdl(void);
|
||||
|
||||
void vfs_print_tree_recurse(SDL_RWops *dest, VFSNode *root, char *prefix);
|
||||
|
||||
#endif
|
217
src/vfs/public.c
Normal file
217
src/vfs/public.c
Normal file
|
@ -0,0 +1,217 @@
|
|||
|
||||
#include "private.h"
|
||||
#include "union.h"
|
||||
#include "syspath.h"
|
||||
|
||||
typedef struct VFSDir {
|
||||
VFSNode *node;
|
||||
void *opaque;
|
||||
} VFSDir;
|
||||
|
||||
bool vfs_create_union_mountpoint(const char *mountpoint) {
|
||||
VFSNode *unode = vfs_alloc();
|
||||
vfs_union_init(unode);
|
||||
|
||||
if(!vfs_mount(vfs_root, mountpoint, unode)) {
|
||||
vfs_free(unode);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool vfs_mount_syspath(const char *mountpoint, const char *fspath, bool mkdir) {
|
||||
VFSNode *rdir = vfs_alloc();
|
||||
vfs_syspath_init(rdir, fspath);
|
||||
assert(rdir->funcs);
|
||||
assert(rdir->funcs->mkdir);
|
||||
|
||||
if(mkdir && !rdir->funcs->mkdir(rdir, NULL)) {
|
||||
vfs_set_error("Can't create directory: %s", vfs_get_error());
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!vfs_mount(vfs_root, mountpoint, rdir)) {
|
||||
vfs_free(rdir);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
SDL_RWops* vfs_open(const char *path, VFSOpenMode mode) {
|
||||
SDL_RWops *rwops = NULL;
|
||||
char p[strlen(path)+1];
|
||||
path = vfs_path_normalize(path, p);
|
||||
VFSNode *node = vfs_locate(vfs_root, path);
|
||||
|
||||
if(node) {
|
||||
assert(node->funcs != NULL);
|
||||
|
||||
if(node->funcs->open) {
|
||||
// expected to set error on failure
|
||||
rwops = node->funcs->open(node, mode);
|
||||
vfs_locate_cleanup(vfs_root, node);
|
||||
} else {
|
||||
vfs_set_error("Node '%s' can't be opened as a file", path);
|
||||
}
|
||||
} else {
|
||||
vfs_set_error("Node '%s' does not exist", path);
|
||||
}
|
||||
|
||||
return rwops;
|
||||
}
|
||||
|
||||
VFSInfo vfs_query(const char *path) {
|
||||
char p[strlen(path)+1];
|
||||
path = vfs_path_normalize(path, p);
|
||||
VFSNode *node = vfs_locate(vfs_root, path);
|
||||
|
||||
if(node) {
|
||||
// expected to set error on failure
|
||||
// not that e.g. a file not existing on a real filesystem
|
||||
// is not an error condition. If we can't tell whether it
|
||||
// exists or not, that is an error.
|
||||
|
||||
VFSInfo i = vfs_query_node(node);
|
||||
vfs_locate_cleanup(vfs_root, node);
|
||||
return i;
|
||||
}
|
||||
|
||||
vfs_set_error("Node '%s' does not exist", path);
|
||||
return VFSINFO_ERROR;
|
||||
}
|
||||
|
||||
bool vfs_mkdir(const char *path) {
|
||||
char p[strlen(path)+1];
|
||||
path = vfs_path_normalize(path, p);
|
||||
VFSNode *node = vfs_locate(vfs_root, path);
|
||||
|
||||
if(node && node->funcs->mkdir) {
|
||||
return node->funcs->mkdir(node, NULL);
|
||||
}
|
||||
|
||||
char *parent, *subdir;
|
||||
vfs_path_split_right(p, &parent, &subdir);
|
||||
node = vfs_locate(vfs_root, parent);
|
||||
|
||||
if(node) {
|
||||
if(node->funcs->mkdir) {
|
||||
return node->funcs->mkdir(node, subdir);
|
||||
} else {
|
||||
vfs_set_error("Node '%s' does not support creation of subdirectories", parent);
|
||||
}
|
||||
} else {
|
||||
vfs_set_error("Node '%s' does not exist", parent);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void vfs_mkdir_required(const char *path) {
|
||||
if(!vfs_mkdir(path)) {
|
||||
log_fatal("%s", vfs_get_error());
|
||||
}
|
||||
}
|
||||
|
||||
static char* vfs_syspath_node(VFSNode *node, const char *path) {
|
||||
assert(node->funcs);
|
||||
char *p = NULL;
|
||||
|
||||
if(node->funcs->syspath) {
|
||||
// expected to set error on failure
|
||||
p = node->funcs->syspath(node);
|
||||
} else {
|
||||
vfs_set_error("Node '%s' does not represent a real filesystem path", path);
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
static char* vfs_syspath_internal(const char *path, bool repr) {
|
||||
char p[strlen(path)+1];
|
||||
path = vfs_path_normalize(path, p);
|
||||
VFSNode *node = vfs_locate(vfs_root, path);
|
||||
|
||||
if(node) {
|
||||
char *p = vfs_syspath_node(node, path);
|
||||
|
||||
if(!p && repr) {
|
||||
p = vfs_repr(node);
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
vfs_set_error("Node '%s' does not exist", path);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char* vfs_syspath(const char *path) {
|
||||
return vfs_syspath_internal(path, false);
|
||||
}
|
||||
|
||||
char* vfs_syspath_or_repr(const char *path) {
|
||||
return vfs_syspath_internal(path, true);
|
||||
}
|
||||
|
||||
bool vfs_print_tree(SDL_RWops *dest, char *path) {
|
||||
char p[strlen(path)+3], *ignore;
|
||||
path = vfs_path_normalize(path, p);
|
||||
VFSNode *node = vfs_locate(vfs_root, path);
|
||||
|
||||
if(!node) {
|
||||
vfs_set_error("Node '%s' does not exist", path);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(*path) {
|
||||
vfs_path_split_right(path, &path, &ignore);
|
||||
if(*path) {
|
||||
char *e = strchr(path, 0);
|
||||
*e++ = '/';
|
||||
*e = 0;
|
||||
}
|
||||
vfs_path_root_prefix(path);
|
||||
}
|
||||
|
||||
vfs_print_tree_recurse(dest, node, path);
|
||||
return true;
|
||||
}
|
||||
|
||||
VFSDir* vfs_dir_open(const char *path) {
|
||||
char p[strlen(path)+1];
|
||||
path = vfs_path_normalize(path, p);
|
||||
VFSNode *node = vfs_locate(vfs_root, path);
|
||||
|
||||
if(node) {
|
||||
if(node->funcs->iter && vfs_query_node(node).is_dir) {
|
||||
VFSDir *d = calloc(1, sizeof(VFSDir));
|
||||
d->node = node;
|
||||
return d;
|
||||
} else {
|
||||
vfs_set_error("Node '%s' is not a directory", path);
|
||||
}
|
||||
|
||||
vfs_locate_cleanup(vfs_root, node);
|
||||
} else {
|
||||
vfs_set_error("Node '%s' does not exist", path);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void vfs_dir_close(VFSDir *dir) {
|
||||
if(dir) {
|
||||
vfs_iter_stop(dir->node, &dir->opaque);
|
||||
vfs_locate_cleanup(vfs_root, dir->node);
|
||||
}
|
||||
}
|
||||
|
||||
const char* vfs_dir_read(VFSDir *dir) {
|
||||
if(dir) {
|
||||
return vfs_iter(dir->node, &dir->opaque);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
44
src/vfs/public.h
Normal file
44
src/vfs/public.h
Normal file
|
@ -0,0 +1,44 @@
|
|||
|
||||
#ifndef TAISEI_VFS_PUBLIC
|
||||
#define TAISEI_VFS_PUBLIC
|
||||
|
||||
#include <SDL.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef struct VFSInfo {
|
||||
unsigned int error: 1;
|
||||
unsigned int exists : 1;
|
||||
unsigned int is_dir : 1;
|
||||
} VFSInfo;
|
||||
|
||||
#define VFSINFO_ERROR ((VFSInfo){.error = true, 0})
|
||||
|
||||
typedef enum VFSOpenMode {
|
||||
VFS_MODE_READ = 1,
|
||||
VFS_MODE_WRITE = 2,
|
||||
} VFSOpenMode;
|
||||
|
||||
typedef struct VFSDir VFSDir;
|
||||
|
||||
SDL_RWops* vfs_open(const char *path, VFSOpenMode mode);
|
||||
VFSInfo vfs_query(const char *path);
|
||||
bool vfs_mkdir(const char *path);
|
||||
void vfs_mkdir_required(const char *path);
|
||||
|
||||
bool vfs_create_union_mountpoint(const char *mountpoint);
|
||||
bool vfs_mount_syspath(const char *mountpoint, const char *fspath, bool mkdir);
|
||||
|
||||
VFSDir* vfs_dir_open(const char *path);
|
||||
void vfs_dir_close(VFSDir *dir);
|
||||
const char* vfs_dir_read(VFSDir *dir);
|
||||
|
||||
char* vfs_syspath(const char *path);
|
||||
char* vfs_syspath_or_repr(const char *path);
|
||||
bool vfs_print_tree(SDL_RWops *dest, char *path);
|
||||
|
||||
// these are defined in private.c, but need to be accessible from external code
|
||||
void vfs_init(void);
|
||||
void vfs_uninit(void);
|
||||
const char* vfs_get_error(void);
|
||||
|
||||
#endif
|
9
src/vfs/syspath.h
Normal file
9
src/vfs/syspath.h
Normal file
|
@ -0,0 +1,9 @@
|
|||
|
||||
#ifndef TAISEI_VFS_SYSPATH
|
||||
#define TAISEI_VFS_SYSPATH
|
||||
|
||||
#include "private.h"
|
||||
|
||||
void vfs_syspath_init(VFSNode *node, const char *path);
|
||||
|
||||
#endif
|
143
src/vfs/syspath_posix.c
Normal file
143
src/vfs/syspath_posix.c
Normal file
|
@ -0,0 +1,143 @@
|
|||
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "syspath.h"
|
||||
|
||||
static void vfs_syspath_init_internal(VFSNode *node, char *path, char *name);
|
||||
|
||||
static void vfs_syspath_free(VFSNode *node) {
|
||||
free(node->syspath.path);
|
||||
}
|
||||
|
||||
static VFSInfo vfs_syspath_query(VFSNode *node) {
|
||||
struct stat fstat;
|
||||
VFSInfo i = {0};
|
||||
|
||||
if(stat(node->syspath.path, &fstat) >= 0) {
|
||||
i.exists = true;
|
||||
i.is_dir = S_ISDIR(fstat.st_mode);
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
static SDL_RWops* vfs_syspath_open(VFSNode *node, VFSOpenMode mode) {
|
||||
SDL_RWops *rwops = SDL_RWFromFile(node->syspath.path, mode == VFS_MODE_WRITE ? "w" : "r");
|
||||
|
||||
if(!rwops) {
|
||||
vfs_set_error_from_sdl();
|
||||
}
|
||||
|
||||
return rwops;
|
||||
}
|
||||
|
||||
static VFSNode* vfs_syspath_locate(VFSNode *node, const char *path) {
|
||||
VFSNode *n = vfs_alloc();
|
||||
char buf[strlen(path)+1], *base, *name;
|
||||
strcpy(buf, path);
|
||||
vfs_path_split_right(buf, &base, &name);
|
||||
vfs_syspath_init_internal(n, strfmt("%s%c%s", node->syspath.path, VFS_PATH_SEP, path), strdup(name));
|
||||
return n;
|
||||
}
|
||||
|
||||
static const char* vfs_syspath_iter(VFSNode *node, void **opaque) {
|
||||
DIR *dir;
|
||||
struct dirent *e;
|
||||
|
||||
if(!*opaque) {
|
||||
*opaque = opendir(node->syspath.path);
|
||||
}
|
||||
|
||||
if(!(dir = *opaque)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
do {
|
||||
e = readdir(dir);
|
||||
} while(e && (!strcmp(e->d_name, ".") || !strcmp(e->d_name, "..")));
|
||||
|
||||
if(e) {
|
||||
return e->d_name;
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
*opaque = NULL;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void vfs_syspath_iter_stop(VFSNode *node, void **opaque) {
|
||||
if(*opaque) {
|
||||
closedir((DIR*)*opaque);
|
||||
*opaque = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static char* vfs_syspath_repr(VFSNode *node) {
|
||||
return strfmt("filesystem path (posix): %s", node->syspath.path);
|
||||
}
|
||||
|
||||
static char* vfs_syspath_syspath(VFSNode *node) {
|
||||
return strdup(node->syspath.path);
|
||||
}
|
||||
|
||||
// TODO: get rid of this when the win32 version of this module is implemented
|
||||
#ifndef __POSIX__
|
||||
#define mkdir(p,m) mkdir(p)
|
||||
#else
|
||||
#define mkdir(p,m) mkdir(p, m)
|
||||
#endif
|
||||
|
||||
static bool vfs_syspath_mkdir(VFSNode *node, const char *subdir) {
|
||||
if(!subdir) {
|
||||
subdir = "";
|
||||
}
|
||||
|
||||
char *p = strfmt("%s%c%s", node->syspath.path, VFS_PATH_SEP, subdir);
|
||||
bool ok = !mkdir(p, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
|
||||
|
||||
if(!ok && errno == EEXIST) {
|
||||
VFSNode *n = vfs_locate(node, subdir);
|
||||
|
||||
if(n && vfs_query_node(n).is_dir) {
|
||||
ok = true;
|
||||
}
|
||||
|
||||
if(node != n) {
|
||||
vfs_locate_cleanup(vfs_root, n);
|
||||
}
|
||||
}
|
||||
|
||||
if(!ok) {
|
||||
vfs_set_error("Can't create directory %s (errno: %i)", p, errno);
|
||||
}
|
||||
|
||||
free(p);
|
||||
return ok;
|
||||
}
|
||||
|
||||
static VFSNodeFuncs vfs_funcs_syspath = {
|
||||
.repr = vfs_syspath_repr,
|
||||
.query = vfs_syspath_query,
|
||||
.free = vfs_syspath_free,
|
||||
.locate = vfs_syspath_locate,
|
||||
.syspath = vfs_syspath_syspath,
|
||||
.iter = vfs_syspath_iter,
|
||||
.iter_stop = vfs_syspath_iter_stop,
|
||||
.mkdir = vfs_syspath_mkdir,
|
||||
.open = vfs_syspath_open,
|
||||
};
|
||||
|
||||
static void vfs_syspath_init_internal(VFSNode *node, char *path, char *name) {
|
||||
node->type = VNODE_SYSPATH;
|
||||
node->funcs = &vfs_funcs_syspath;
|
||||
node->syspath.path = path;
|
||||
node->name = name;
|
||||
}
|
||||
|
||||
void vfs_syspath_init(VFSNode *node, const char *path) {
|
||||
vfs_syspath_init_internal(node, strdup(path), NULL);
|
||||
}
|
2
src/vfs/syspath_win32.c
Normal file
2
src/vfs/syspath_win32.c
Normal file
|
@ -0,0 +1,2 @@
|
|||
|
||||
#error Not yet implemented
|
248
src/vfs/union.c
Normal file
248
src/vfs/union.c
Normal file
|
@ -0,0 +1,248 @@
|
|||
|
||||
#include "union.h"
|
||||
|
||||
static bool vfs_union_mount_internal(VFSNode *unode, const char *mountpoint, VFSNode *mountee, VFSInfo info, bool seterror);
|
||||
|
||||
static void vfs_union_delete_callback(void **list, void *elem) {
|
||||
ListContainer *c = elem;
|
||||
VFSNode *n = c->data;
|
||||
vfs_free(n);
|
||||
delete_element(list, elem);
|
||||
}
|
||||
|
||||
static void vfs_union_free(VFSNode *node) {
|
||||
delete_all_elements((void**)&node->vunion.members, vfs_union_delete_callback);
|
||||
}
|
||||
|
||||
static VFSNode* vfs_union_locate(VFSNode *node, const char *path) {
|
||||
VFSNode *u = vfs_alloc();
|
||||
vfs_union_init(u); // uniception!
|
||||
|
||||
VFSInfo prim_info = VFSINFO_ERROR;
|
||||
ListContainer *first = node->vunion.members.all;
|
||||
ListContainer *last = first;
|
||||
ListContainer *c;
|
||||
|
||||
for(c = first; c; c = c->next) {
|
||||
last = c;
|
||||
}
|
||||
|
||||
for(c = last; c; c = c->prev) {
|
||||
VFSNode *n = c->data;
|
||||
VFSNode *o = vfs_locate(n, path);
|
||||
|
||||
if(o) {
|
||||
VFSInfo i = vfs_query_node(o);
|
||||
|
||||
if(vfs_union_mount_internal(u, NULL, o, i, false)) {
|
||||
prim_info = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(u->vunion.members.primary) {
|
||||
if(!u->vunion.members.all->next || !prim_info.is_dir) {
|
||||
// the temporary union contains just one member, or doesn't represent a directory
|
||||
// in those cases it's just a useless wrapper, so let's just return the primary member directly
|
||||
VFSNode *n = u->vunion.members.primary;
|
||||
|
||||
// remove the primary member from the 'all' list to prevent vfs_free from wrecking it
|
||||
delete_element((void**)&u->vunion.members.all, u->vunion.members.all);
|
||||
|
||||
vfs_free(u);
|
||||
return n;
|
||||
}
|
||||
|
||||
u->name = strdup(u->vunion.members.primary->name);
|
||||
} else {
|
||||
// all in vain...
|
||||
vfs_free(u);
|
||||
u = NULL;
|
||||
}
|
||||
|
||||
return u;
|
||||
}
|
||||
|
||||
typedef struct VFSUnionIterData {
|
||||
Hashtable *visited;
|
||||
ListContainer *current;
|
||||
void *opaque;
|
||||
} VFSUnionIterData;
|
||||
|
||||
static const char* vfs_union_iter(VFSNode *node, void **opaque) {
|
||||
VFSUnionIterData *i = *opaque;
|
||||
const char *r = NULL;
|
||||
|
||||
if(!i) {
|
||||
i = malloc(sizeof(VFSUnionIterData));
|
||||
i->current = node->vunion.members.all;
|
||||
i->opaque = NULL;
|
||||
|
||||
// XXX: this may not be the most efficient implementation of a "set" structure...
|
||||
i->visited = hashtable_new_stringkeys(37);
|
||||
|
||||
*opaque = i;
|
||||
}
|
||||
|
||||
while(i->current) {
|
||||
VFSNode *n = (VFSNode*)i->current->data;
|
||||
r = vfs_iter(n, &i->opaque);
|
||||
|
||||
if(!r) {
|
||||
vfs_iter_stop(n, &i->opaque);
|
||||
i->opaque = NULL;
|
||||
i->current = i->current->next;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(hashtable_get_string(i->visited, r)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
hashtable_set_string(i->visited, r, (void*)true);
|
||||
break;
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
static void vfs_union_iter_stop(VFSNode *node, void **opaque) {
|
||||
VFSUnionIterData *i = *opaque;
|
||||
|
||||
if(i) {
|
||||
hashtable_free(i->visited);
|
||||
free(i);
|
||||
}
|
||||
|
||||
*opaque = NULL;
|
||||
}
|
||||
|
||||
static VFSInfo vfs_union_query(VFSNode *node) {
|
||||
if(node->vunion.members.primary) {
|
||||
return vfs_query_node(node->vunion.members.primary);
|
||||
}
|
||||
|
||||
vfs_set_error("Union object has no members");
|
||||
return VFSINFO_ERROR;
|
||||
}
|
||||
|
||||
static bool vfs_union_mount_internal(VFSNode *unode, const char *mountpoint, VFSNode *mountee, VFSInfo info, bool seterror) {
|
||||
if(!info.exists) {
|
||||
if(seterror) {
|
||||
vfs_set_error("Mountee doesn't represent a usable resource");
|
||||
} else {
|
||||
vfs_free(mountee);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if(seterror && !info.is_dir) {
|
||||
vfs_set_error("Mountee is not a directory");
|
||||
return false;
|
||||
}
|
||||
|
||||
ListContainer *c = NULL, *p = unode->vunion.members.all;
|
||||
create_container(&c)->data = mountee;
|
||||
|
||||
c->next = p;
|
||||
|
||||
if(p) {
|
||||
p->prev = c;
|
||||
}
|
||||
|
||||
unode->vunion.members.all = c;
|
||||
unode->vunion.members.primary = mountee;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool vfs_union_mount(VFSNode *unode, const char *mountpoint, VFSNode *mountee) {
|
||||
if(mountpoint) {
|
||||
vfs_set_error("Attempted to use a named mountpoint on a union");
|
||||
return false;
|
||||
}
|
||||
|
||||
return vfs_union_mount_internal(unode, NULL, mountee, vfs_query_node(mountee), true);
|
||||
}
|
||||
|
||||
static SDL_RWops* vfs_union_open(VFSNode *unode, VFSOpenMode mode) {
|
||||
VFSNode *n = unode->vunion.members.primary;
|
||||
|
||||
if(n) {
|
||||
if(n->funcs->open) {
|
||||
return n->funcs->open(n, mode);
|
||||
} else {
|
||||
vfs_set_error("Primary union member can't be opened as a file");
|
||||
}
|
||||
} else {
|
||||
vfs_set_error("Union object has no members");
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static char* vfs_union_repr(VFSNode *node) {
|
||||
char *mlist = strdup("union: "), *r;
|
||||
|
||||
for(ListContainer *c = node->vunion.members.all; c; c = c->next) {
|
||||
VFSNode *n = c->data;
|
||||
|
||||
strappend(&mlist, r = vfs_repr(n));
|
||||
free(r);
|
||||
|
||||
if(c->next) {
|
||||
strappend(&mlist, ", ");
|
||||
}
|
||||
}
|
||||
|
||||
return mlist;
|
||||
}
|
||||
|
||||
static char* vfs_union_syspath(VFSNode *node) {
|
||||
VFSNode *n = node->vunion.members.primary;
|
||||
|
||||
if(n) {
|
||||
if(n->funcs->syspath) {
|
||||
return n->funcs->syspath(node);
|
||||
} else {
|
||||
vfs_set_error("Primary union member doesn't represent a real filesystem path");
|
||||
}
|
||||
}
|
||||
|
||||
vfs_set_error("Union object has no members");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool vfs_union_mkdir(VFSNode *node, const char *subdir) {
|
||||
VFSNode *n = node->vunion.members.primary;
|
||||
|
||||
if(n) {
|
||||
if(n->funcs->mkdir) {
|
||||
return n->funcs->mkdir(node, subdir);
|
||||
} else {
|
||||
vfs_set_error("Primary union member doesn't support directory creation");
|
||||
}
|
||||
}
|
||||
|
||||
vfs_set_error("Union object has no members");
|
||||
return false;
|
||||
}
|
||||
|
||||
static VFSNodeFuncs vfs_funcs_union = {
|
||||
.repr = vfs_union_repr,
|
||||
.query = vfs_union_query,
|
||||
.free = vfs_union_free,
|
||||
.syspath = vfs_union_syspath,
|
||||
.mount = vfs_union_mount,
|
||||
.locate = vfs_union_locate,
|
||||
.iter = vfs_union_iter,
|
||||
.iter_stop = vfs_union_iter_stop,
|
||||
.mkdir = vfs_union_mkdir,
|
||||
.open = vfs_union_open,
|
||||
};
|
||||
|
||||
void vfs_union_init(VFSNode *node) {
|
||||
node->type = VNODE_UNION;
|
||||
node->funcs = &vfs_funcs_union;
|
||||
}
|
9
src/vfs/union.h
Normal file
9
src/vfs/union.h
Normal file
|
@ -0,0 +1,9 @@
|
|||
|
||||
#ifndef TAISEI_VFS_UNION
|
||||
#define TAISEI_VFS_UNION
|
||||
|
||||
#include "private.h"
|
||||
|
||||
void vfs_union_init(VFSNode *node);
|
||||
|
||||
#endif
|
117
src/vfs/vdir.c
Normal file
117
src/vfs/vdir.c
Normal file
|
@ -0,0 +1,117 @@
|
|||
|
||||
#include "vdir.h"
|
||||
|
||||
static void vfs_vdir_detach_node(VFSNode *vdir, VFSNode *node, bool unset) {
|
||||
assert(node->parent == vdir);
|
||||
|
||||
if(unset) {
|
||||
hashtable_unset_string(vdir->vdir.contents, node->name);
|
||||
}
|
||||
|
||||
free(node->name);
|
||||
node->name = NULL;
|
||||
}
|
||||
|
||||
static void vfs_vdir_attach_node(VFSNode *vdir, const char *name, VFSNode *node) {
|
||||
assert(node->parent == NULL);
|
||||
assert(node->name == NULL);
|
||||
|
||||
VFSNode *oldnode = hashtable_get_string(vdir->vdir.contents, name);
|
||||
|
||||
if(oldnode) {
|
||||
vfs_vdir_detach_node(vdir, oldnode, true);
|
||||
}
|
||||
|
||||
node->name = strdup(name);
|
||||
node->parent = vdir;
|
||||
|
||||
hashtable_set_string(vdir->vdir.contents, name, node);
|
||||
}
|
||||
|
||||
static VFSNode* vfs_vdir_locate(VFSNode *vdir, const char *path) {
|
||||
VFSNode *node;
|
||||
char mutpath[strlen(path)+1];
|
||||
char *primpath, *subpath;
|
||||
|
||||
assert(vdir->type == VNODE_VDIR);
|
||||
|
||||
strcpy(mutpath, path);
|
||||
vfs_path_split_left(mutpath, &primpath, &subpath);
|
||||
|
||||
if(node = hashtable_get_string(vdir->vdir.contents, mutpath)) {
|
||||
return vfs_locate(node, subpath);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const char* vfs_vdir_iter(VFSNode *vdir, void **opaque) {
|
||||
char *ret = NULL;
|
||||
|
||||
if(!*opaque) {
|
||||
*opaque = hashtable_iter(vdir->vdir.contents);
|
||||
}
|
||||
|
||||
if(!hashtable_iter_next((HashtableIterator*)*opaque, (void**)&ret, NULL)) {
|
||||
*opaque = NULL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void vfs_vdir_iter_stop(VFSNode *vdir, void **opaque) {
|
||||
if(*opaque) {
|
||||
free(*opaque);
|
||||
*opaque = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static VFSInfo vfs_vdir_query(VFSNode *vdir) {
|
||||
return (VFSInfo) {
|
||||
.exists = true,
|
||||
.is_dir = true,
|
||||
};
|
||||
}
|
||||
|
||||
static void vfs_vdir_free(VFSNode *vdir) {
|
||||
Hashtable *ht = vdir->vdir.contents;
|
||||
HashtableIterator *i;
|
||||
VFSNode *child;
|
||||
|
||||
for(i = hashtable_iter(ht); hashtable_iter_next(i, NULL, (void**)&child);) {
|
||||
vfs_vdir_detach_node(vdir, child, false);
|
||||
vfs_free(child);
|
||||
}
|
||||
|
||||
hashtable_free(ht);
|
||||
}
|
||||
|
||||
static bool vfs_vdir_mount(VFSNode *vdir, const char *mountpoint, VFSNode *subtree) {
|
||||
if(!mountpoint) {
|
||||
// merge attempt, unsupported
|
||||
return false;
|
||||
}
|
||||
|
||||
vfs_vdir_attach_node(vdir, mountpoint, subtree);
|
||||
return true;
|
||||
}
|
||||
|
||||
static char* vfs_vdir_repr(VFSNode *node) {
|
||||
return strdup("virtual directory");
|
||||
}
|
||||
|
||||
static VFSNodeFuncs vfs_funcs_vdir = {
|
||||
.repr = vfs_vdir_repr,
|
||||
.query = vfs_vdir_query,
|
||||
.free = vfs_vdir_free,
|
||||
.mount = vfs_vdir_mount,
|
||||
.locate = vfs_vdir_locate,
|
||||
.iter = vfs_vdir_iter,
|
||||
.iter_stop = vfs_vdir_iter_stop,
|
||||
};
|
||||
|
||||
void vfs_vdir_init(VFSNode *node) {
|
||||
node->type = VNODE_VDIR;
|
||||
node->funcs = &vfs_funcs_vdir;
|
||||
node->vdir.contents = hashtable_new_stringkeys(HT_DYNAMIC_SIZE);
|
||||
}
|
9
src/vfs/vdir.h
Normal file
9
src/vfs/vdir.h
Normal file
|
@ -0,0 +1,9 @@
|
|||
|
||||
#ifndef TAISEI_VFS_VDIR
|
||||
#define TAISEI_VFS_VDIR
|
||||
|
||||
#include "private.h"
|
||||
|
||||
void vfs_vdir_init(VFSNode *node);
|
||||
|
||||
#endif
|
15
src/video.c
15
src/video.c
|
@ -265,7 +265,7 @@ void video_setmode(int w, int h, bool fs, bool resizable) {
|
|||
void video_take_screenshot(void) {
|
||||
SDL_RWops *out;
|
||||
char *data;
|
||||
char outfile[128], *outpath;
|
||||
char outfile[128], *outpath, *syspath;
|
||||
time_t rawtime;
|
||||
struct tm * timeinfo;
|
||||
int w, h, rw, rh;
|
||||
|
@ -283,15 +283,18 @@ void video_take_screenshot(void) {
|
|||
|
||||
time(&rawtime);
|
||||
timeinfo = localtime(&rawtime);
|
||||
strftime(outfile, 128, "/taisei_%Y%m%d_%H-%M-%S_%Z.png", timeinfo);
|
||||
strftime(outfile, 128, "taisei_%Y%m%d_%H-%M-%S_%Z.png", timeinfo);
|
||||
|
||||
outpath = strjoin(get_screenshots_path(), outfile, NULL);
|
||||
log_info("Saving screenshot as %s", outpath);
|
||||
out = SDL_RWFromFile(outpath, "w");
|
||||
outpath = strjoin("storage/screenshots/", outfile, NULL);
|
||||
syspath = vfs_syspath_or_repr(outpath);
|
||||
log_info("Saving screenshot as %s", syspath);
|
||||
free(syspath);
|
||||
|
||||
out = vfs_open(outpath, VFS_MODE_WRITE);
|
||||
free(outpath);
|
||||
|
||||
if(!out) {
|
||||
log_warn("SDL_RWFromFile() failed: %s", SDL_GetError());
|
||||
log_warn("VFS error: %s", vfs_get_error());
|
||||
free(data);
|
||||
return;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue