diff options
Diffstat (limited to 'jni/compat')
-rw-r--r-- | jni/compat/README | 2 | ||||
-rw-r--r-- | jni/compat/compat-physfs.c | 291 | ||||
-rw-r--r-- | jni/compat/compat.c | 58 | ||||
-rw-r--r-- | jni/compat/compat.h | 4 |
4 files changed, 355 insertions, 0 deletions
diff --git a/jni/compat/README b/jni/compat/README new file mode 100644 index 0000000..802d0cc --- /dev/null +++ b/jni/compat/README @@ -0,0 +1,2 @@ +Any sort of compatibility hacks can go here. +Typically to implement missing libc stuff on android. 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); + } + } +} + diff --git a/jni/compat/compat.c b/jni/compat/compat.c new file mode 100644 index 0000000..c627f6a --- /dev/null +++ b/jni/compat/compat.c @@ -0,0 +1,58 @@ +#include "compat.h" +#include <unistd.h> +#include <pthread.h> +#include <android/log.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +int wctomb(char *s, wchar_t wc) { return wcrtomb(s,wc,NULL); } +int mbtowc(wchar_t *pwc, const char *s, size_t n) { return mbrtowc(pwc, s, n, NULL); } + +struct stream { + const char *name; + int fd[2]; + FILE *src; +}; + +static void* +log_thread(void *arg) +{ + struct stream *stream = arg; + char buf[4000], *off = buf, *nl; // Can't be too big or android stops logging + for (ssize_t r = 0;;off += r, r = 0) { + if (off - buf < sizeof(buf) - 1) { + errno = 0; + r = read(stream->fd[0], off, (sizeof(buf) - 1) - (off - buf)); + if (r <= 0) { if (errno == EINTR) continue; else break; } + off[r] = 0; + } + if ((nl = strrchr(off, '\n'))) { + *nl = 0; ++nl; + __android_log_write(ANDROID_LOG_INFO, stream->name, buf); + r = (off + r) - nl; + memcpy((off = buf), nl, r); + } else if (off - buf >= sizeof(buf)) { + __android_log_write(ANDROID_LOG_INFO, stream->name, buf); + r = 0; off = buf; + } + } + close(stream->fd[0]); + close(stream->fd[1]); + return NULL; +} + +__attribute__((constructor)) static void +log_init(void) { + static struct stream stream[] = { { .name = "stdout" }, { .name = "stderr" } }; + stream[0].src = stdout; stream[1].src = stderr; + for (size_t i = 0; i < sizeof(stream) / sizeof(stream[0]); ++i) { + setvbuf(stream[i].src, NULL, _IOLBF, BUFSIZ); + pipe(stream[i].fd); + dup2(stream[i].fd[1], fileno(stream[i].src)); + pthread_t thread; + pthread_create(&thread, 0, log_thread, &stream[i]); + pthread_detach(thread); + } + chdir(getenv("COMPAT_CHDIR")); +} diff --git a/jni/compat/compat.h b/jni/compat/compat.h new file mode 100644 index 0000000..b8efd76 --- /dev/null +++ b/jni/compat/compat.h @@ -0,0 +1,4 @@ +#pragma once +#include <wchar.h> +int wctomb(char *s, wchar_t wc); +int mbtowc(wchar_t *pwc, const char *s, size_t n); |