summaryrefslogtreecommitdiff
path: root/jni/compat
diff options
context:
space:
mode:
Diffstat (limited to 'jni/compat')
-rw-r--r--jni/compat/README2
-rw-r--r--jni/compat/compat-physfs.c291
-rw-r--r--jni/compat/compat.c58
-rw-r--r--jni/compat/compat.h4
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);