#include #include #include #include #include #include #include #include #include #include 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); } } }