diff options
author | Jari Vetoniemi <jari.vetoniemi@indooratlas.com> | 2020-03-16 18:49:26 +0900 |
---|---|---|
committer | Jari Vetoniemi <jari.vetoniemi@indooratlas.com> | 2020-03-30 00:39:06 +0900 |
commit | fcbf63e62c627deae76c1b8cb8c0876c536ed811 (patch) | |
tree | 64cb17de3f41a2b6fef2368028fbd00349946994 /jni/compat/compat-physfs.c |
Fresh start
Diffstat (limited to 'jni/compat/compat-physfs.c')
-rw-r--r-- | jni/compat/compat-physfs.c | 291 |
1 files changed, 291 insertions, 0 deletions
diff --git a/jni/compat/compat-physfs.c b/jni/compat/compat-physfs.c new file mode 100644 index 0000000..c63b7c3 --- /dev/null +++ b/jni/compat/compat-physfs.c @@ -0,0 +1,291 @@ +#include <physfs.h> +#include <stdio.h> +#include <dlfcn.h> +#include <dirent.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <assert.h> +#include <sys/stat.h> + +static int +physfs_read(void *cookie, char *buf, int n) { + return PHYSFS_readBytes((PHYSFS_File*)cookie, buf, n); +} + +static int +physfs_write(void *cookie, const char *buf, int n) { + assert(0 && "can't write"); + return PHYSFS_writeBytes((PHYSFS_File*)cookie, buf, n); +} + +static fpos_t +physfs_seek(void *cookie, fpos_t off, int whence) { + PHYSFS_uint64 pos = off; + if (whence == SEEK_CUR) pos = PHYSFS_tell((PHYSFS_File*)cookie) + off; + else if (whence == SEEK_END) pos = PHYSFS_fileLength((PHYSFS_File*)cookie); + PHYSFS_seek((PHYSFS_File*)cookie, pos); + return pos; +} + +static int +physfs_close(void *cookie) { + return (PHYSFS_close((PHYSFS_File*)cookie) ? 0 : -1); +} + +static FILE* (*_fopen)(const char*, const char*); +static int (*_access)(const char*, int); +static int (*_lstat)(const char*, struct stat*); +static int (*_stat)(const char*, struct stat*); + +static const char* +physfs_path(const char *path) { + return (*path == '.' ? path + 1 : path); +} + +FILE* +fopen(const char *path, const char *mode) { + assert(_fopen); + FILE *ff = _fopen(path, mode); + if (ff) return ff; + if (*mode != 'r' || !strcmp(mode, "r+")) return NULL; + PHYSFS_File *f = PHYSFS_openRead(physfs_path(path)); + return (f ? funopen(f, physfs_read, physfs_write, physfs_seek, physfs_close) : NULL); +} + +int +access(const char *path, int mode) { + assert(_access); + if (!_access(path, mode)) return 0; + return ((mode & W_OK) ? -1 : PHYSFS_exists(physfs_path(path))); +} + +static int +physfs_stat(const char *path, struct stat *buf) { + PHYSFS_Stat st = {0}; + if (!PHYSFS_stat(physfs_path(path), &st)) + return -1; + *buf = (struct stat){ + .st_mode = (st.filetype == PHYSFS_FILETYPE_REGULAR ? S_IFREG : + st.filetype == PHYSFS_FILETYPE_DIRECTORY ? S_IFDIR : + st.filetype == PHYSFS_FILETYPE_SYMLINK ? S_IFLNK : 0), + .st_size = st.filesize, + }; + return 0; +} + +int +lstat(const char *path, struct stat *buf) { + assert(_lstat); + int ret = _lstat(path, buf); + return (ret != 0 ? physfs_stat(path, buf) : ret); +} + +int +stat(const char *path, struct stat *buf) { + assert(_stat); + int ret = _stat(path, buf); + return (ret != 0 ? physfs_stat(path, buf) : ret); +} + +static DIR* (*_opendir)(const char*); +static struct dirent* (*_readdir)(DIR*); +static int (*_readdir_r)(DIR*, struct dirent*, struct dirent**); +static long (*_telldir)(DIR*); +static void (*_seekdir)(DIR*, long); +static void (*_rewinddir)(DIR*); +static int (*_closedir)(DIR*); +static int (*_dirfd)(DIR*); + +void* +copysz(const void *ptr, const size_t sz) { + void *cpy = malloc(sz); + memcpy(cpy, ptr, sz); + return cpy; +} +#define copy(x) copysz(&x, sizeof(x)) +#define copystr(x) copysz(x, strlen(x) + 1) + +struct compat_dir { + char magic[8]; + char **entries, **entry; + const char *root; + struct dirent dirent; + DIR *real; +}; + +#define DIR_MAGIC 'C', 'o', 'M', 'p', 'A', 'c', 'T', '1' + +DIR* +opendir(const char *path) { + assert(_opendir); + const char *ppath = physfs_path(path); + struct compat_dir dir = { + .magic = {DIR_MAGIC}, + .root = copystr(ppath), + .entries = PHYSFS_enumerateFiles(ppath), + .real = _opendir(path), + }; + return copy(dir); +} + +struct dirent* +readdir(DIR *dirp) { + assert(_readdir); + if (!memcmp(dirp, (char[]){DIR_MAGIC}, sizeof((char[]){DIR_MAGIC}))) { + struct compat_dir *dir = (void*)dirp; + if (!dir->entry) dir->entry = dir->entries; else if (*dir->entry) ++dir->entry; + if (!*dir->entry) return (dir->real ? _readdir(dir->real) : NULL); + char path[1024]; + snprintf(path, sizeof(path), "%s/%s", dir->root, *dir->entry); + PHYSFS_Stat st = {0}; + PHYSFS_stat(path, &st); + dir->dirent = (struct dirent){ + .d_ino = dir->entry - dir->entries, + .d_off = dir->entry - dir->entries, + .d_reclen = sizeof(struct dirent), + .d_type = (st.filetype == PHYSFS_FILETYPE_REGULAR ? DT_REG : + st.filetype == PHYSFS_FILETYPE_DIRECTORY ? DT_DIR : + st.filetype == PHYSFS_FILETYPE_SYMLINK ? DT_LNK : DT_UNKNOWN), + }; + snprintf(dir->dirent.d_name, sizeof(dir->dirent.d_name), "%s", *dir->entry); + return &dir->dirent; + } + return _readdir(dirp); +} + +int +readdir_r(DIR *dirp, struct dirent *a, struct dirent **b) { + assert(_readdir_r); + if (!memcmp(dirp, (char[]){DIR_MAGIC}, sizeof((char[]){DIR_MAGIC}))) { + assert(0 && "not supported"); + return -1; + } + return _readdir_r(dirp, a, b); +} + +long +telldir(DIR *dirp) { + assert(_telldir); + if (!memcmp(dirp, (char[]){DIR_MAGIC}, sizeof((char[]){DIR_MAGIC}))) { + struct compat_dir *dir = (void*)dirp; + return (dir->entry ? dir->entry - dir->entries : 0); + } + return _telldir(dirp); +} + +void +seekdir(DIR *dirp, long off) { + assert(_seekdir); + if (!memcmp(dirp, (char[]){DIR_MAGIC}, sizeof((char[]){DIR_MAGIC}))) { + struct compat_dir *dir = (void*)dirp; + for (long i = 0; i < off && *dir->entry; ++i) ++dir->entry; + return; + } + _seekdir(dirp, off); +} + +void +rewinddir(DIR *dirp) { + assert(_rewinddir); + if (!memcmp(dirp, (char[]){DIR_MAGIC}, sizeof((char[]){DIR_MAGIC}))) { + struct compat_dir *dir = (void*)dirp; + dir->entry = NULL; + return; + } + _rewinddir(dirp); +} + +int +closedir(DIR *dirp) { + assert(_closedir); + if (!memcmp(dirp, (char[]){DIR_MAGIC}, sizeof((char[]){DIR_MAGIC}))) { + struct compat_dir *dir = (void*)dirp; + PHYSFS_freeList(dir->entries); + if (dir->real) _closedir(dir->real); + free((void*)dir->root); + free(dir); + return 0; + } + return _closedir(dirp); +} + +int +dirfd(DIR *dirp) { + assert(_dirfd); + if (!memcmp(dirp, (char[]){DIR_MAGIC}, sizeof((char[]){DIR_MAGIC}))) { + assert(0 && "not supported"); + return -1; + } + return _dirfd(dirp); +} + +static void +dirname(char *path) { + char *ret; + if (!(ret = strrchr(path, '/'))) { + *path = 0; + return; + } + *ret = 0; +} + +static bool +find_file(const char *root, const char *file, char res_buf[], size_t res_buf_sz) { + char **rc; + if (!(rc = PHYSFS_enumerateFiles(root))) + return false; + + for (char **i = rc; *i != NULL; ++i) { + char fullpath[1024]; + snprintf(fullpath, sizeof(fullpath), "%s/%s", root, *i); + struct PHYSFS_Stat st = {0}; + PHYSFS_stat(fullpath, &st); + if (st.filetype == PHYSFS_FILETYPE_DIRECTORY) { + if (find_file(fullpath, file, res_buf, res_buf_sz)) { + PHYSFS_freeList(rc); + return true; + } + } else { + if (strcmp(file, *i)) continue; + snprintf(res_buf, res_buf_sz, "%s", fullpath); + PHYSFS_freeList(rc); + return true; + } + } + + PHYSFS_freeList(rc); + return false; +} + +__attribute__((constructor)) static void +physfs_init(void) { +#define WRAP(x) _##x = dlsym(RTLD_NEXT, #x) + WRAP(fopen); + WRAP(access); + WRAP(lstat); + WRAP(stat); + WRAP(opendir); + WRAP(readdir); + WRAP(readdir_r); + WRAP(telldir); + WRAP(seekdir); + WRAP(rewinddir); + WRAP(closedir); + WRAP(dirfd); +#undef WRAP + chdir(getenv("COMPAT_CHDIR")); + PHYSFS_init("compat-physfs"); + const char *file = getenv("COMPAT_PHYSFS_FILE"), *find = getenv("COMPAT_PHYSFS_FIND"); + if (!file) return; + PHYSFS_mount(file, NULL, 1); + if (find) { + char res[1024]; + if (find_file("", find, res, sizeof(res))) { + dirname(res); + PHYSFS_setRoot(file, res); + } + } +} + |