/* * 2ooM: The Master of Orion II Reverse Engineering Project * Library for working with LBX archive files. * Copyright © 2006-2010, 2013-2014 Nick Bowler * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include "pack.h" #include "misc.h" #include "error.h" #include "lbx.h" #define LBX_MAGIC 0x0000fead #define LBX_HDR_SIZE 8 struct lbx_priv { struct lbx pub; char *name; const struct lbx_file_ops *fops; int (*dtor)(void *handle); void *f; struct lbx_file_state *last_file; unsigned long offsets[FLEXIBLE_ARRAY_MEMBER]; }; struct lbx_file_state { unsigned long base, limit, offset; struct lbx_priv *lbx; int eof; }; static struct lbx_priv *lbx_init(unsigned char *hdr) { unsigned short nfiles = unpack_16_le(hdr+0); unsigned long magic = unpack_32_le(hdr+2); unsigned short version = unpack_16_le(hdr+6); struct lbx_priv *lbx; if (magic != LBX_MAGIC) { lbx_error_raise(LBX_EMAGIC); return NULL; } lbx = malloc(sizeof *lbx + sizeof lbx->offsets[0] * (nfiles+1)); if (!lbx) { lbx_error_raise(LBX_ENOMEM); return NULL; } *lbx = (struct lbx_priv) { .pub = { .nfiles = nfiles, }, }; return lbx; } static char *str_dup(const char *s) { char *buf; buf = malloc(strlen(s)+1); if (buf) strcpy(buf, s); return buf; } struct lbx *lbx_open(void *f, const struct lbx_file_ops *fops, int (*destructor)(void *), const char *name) { unsigned char hdr_buf[LBX_HDR_SIZE]; struct lbx_priv *lbx = NULL; char *dupname = NULL; dupname = str_dup(name); if (!dupname) { lbx_error_raise(LBX_ENOMEM); goto err; } if (fops->read(hdr_buf, sizeof hdr_buf, f) != sizeof hdr_buf) { if (fops->eof(f)) lbx_error_raise(LBX_EEOF); goto err; } lbx = lbx_init(hdr_buf); if (!lbx) goto err; lbx->name = dupname; lbx->dtor = destructor; lbx->fops = fops; lbx->f = f; for (unsigned i = 0; i <= lbx->pub.nfiles; i++) { unsigned char buf[4]; if (fops->read(buf, sizeof buf, f) != sizeof buf) { if (fops->eof(f)) lbx_error_raise(LBX_EEOF); goto err; } lbx->offsets[i] = unpack_32_le(buf); } return &lbx->pub; err: free(dupname); free(lbx); return NULL; } static int file_close(void *f) { return fclose((FILE *)f); } static int pipe_close(void *f) { struct lbx_pipe_state *p = f; int rc; rc = fclose(p->f); free(p); return rc; } static char *last_component(const char *name) { char *c; /* TODO: Handle other path separators. */ c = strrchr(name, '/'); if (!c) return (char *)name; return c+1; } struct lbx *lbx_fopen(const char *file) { const char *name = last_component(file); struct lbx_pipe_state *p; FILE *f; f = fopen(file, "rb"); if (!f) { lbx_error_raise(-errno); return NULL; } if (fseek(f, 0, SEEK_CUR) == 0) return lbx_open(f, &lbx_default_fops, file_close, name); p = malloc(sizeof *p); if (!p) { lbx_error_raise(LBX_ENOMEM); fclose(f); return NULL; } *p = (struct lbx_pipe_state) { .f = f }; return lbx_open(p, &lbx_pipe_fops, pipe_close, name); } int lbx_file_stat(struct lbx *pub, unsigned fileno, struct lbx_statbuf *buf) { struct lbx_priv *lbx = (struct lbx_priv *)pub; static char str[256]; /* FIXME */ if (fileno >= lbx->pub.nfiles) { lbx_error_raise(LBX_EINVAL); buf->name = NULL; return -1; } snprintf(str, sizeof str, "%s.%03u", lbx->name, fileno); buf->name = str; buf->size = lbx->offsets[fileno+1] - lbx->offsets[fileno]; return 0; } int lbx_close(struct lbx *pub) { struct lbx_priv *lbx = (struct lbx_priv *)pub; int rc = 0; if (lbx && lbx->dtor) rc = lbx->dtor(lbx->f); free(lbx->name); free(lbx); return rc; } struct lbx_file_state *lbx_file_open(struct lbx *pub, unsigned fileno) { struct lbx_priv *lbx = (struct lbx_priv *)pub; struct lbx_file_state *state; if (fileno >= lbx->pub.nfiles) { lbx_error_raise(LBX_EINVAL); return NULL; } lbx->last_file = NULL; if (lbx->fops->seek(lbx->f, lbx->offsets[fileno], SEEK_SET) != 0) { return NULL; } state = malloc(sizeof *state); if (!state) { lbx_error_raise(LBX_ENOMEM); return NULL; } *state = (struct lbx_file_state) { .base = lbx->offsets[fileno], .limit = lbx->offsets[fileno+1] - lbx->offsets[fileno], .lbx = lbx, }; lbx->last_file = state; return state; } size_t lbx_file_read(struct lbx_file_state *f, void *buf, size_t n) { const struct lbx_file_ops *fops = f->lbx->fops; size_t want = MIN(n, f->limit - f->offset); size_t rc; if (f != f->lbx->last_file) { f->lbx->last_file = NULL; if (fops->seek(f->lbx->f, f->base + f->limit, SEEK_SET) != 0) return 0; f->lbx->last_file = f; } rc = fops->read(buf, want, f->lbx->f); f->offset += rc; if (rc < want) { if (fops->eof(f->lbx->f)) lbx_error_raise(LBX_EEOF); } else if (rc < n) { f->eof = 1; } return rc; } int lbx_file_seek(struct lbx_file_state *f, long offset, int whence) { const struct lbx_file_ops *fops = f->lbx->fops; unsigned long pos; switch (whence) { case SEEK_CUR: pos = f->offset + offset; break; case SEEK_SET: pos = offset; break; case SEEK_END: pos = f->limit + offset; break; default: lbx_error_raise(LBX_EINVAL); return -1; } if (pos > f->limit) { lbx_error_raise(LBX_EINVAL); return -1; } f->lbx->last_file = NULL; if (fops->seek(f->lbx->f, f->base + pos, SEEK_SET) != 0) return -1; f->offset = pos; f->lbx->last_file = f; f->eof = 0; return 0; } long lbx_file_tell(struct lbx_file_state *f) { return f->offset; } int lbx_file_eof(struct lbx_file_state *f) { return f->eof; } void lbx_file_close(struct lbx_file_state *f) { if (f->lbx->last_file == f) f->lbx->last_file = NULL; free(f); }