585 lines
10 KiB
C
585 lines
10 KiB
C
#include "externals.h"
|
|
#include "internals.h"
|
|
#include "idf.h"
|
|
#include "list.h"
|
|
#include <SDL_rwops.h>
|
|
|
|
typedef struct _idfd_t {
|
|
struct list_head list;
|
|
unsigned long offset;
|
|
unsigned long size;
|
|
idf_t idf;
|
|
} idfd_t;
|
|
|
|
|
|
struct _idff_t {
|
|
struct list_head list;
|
|
idfd_t *dir;
|
|
unsigned long pos;
|
|
FILE *fd;
|
|
};
|
|
|
|
static void free_idfd(void *p)
|
|
{
|
|
idfd_t *dir = (idfd_t*)p;
|
|
if (!p)
|
|
return;
|
|
|
|
while (!list_empty(&dir->list)) {
|
|
idff_t idff;
|
|
idff = (idff_t)(dir->list.next);
|
|
idf_close(idff);
|
|
}
|
|
|
|
free(p);
|
|
}
|
|
|
|
struct _idf_t {
|
|
unsigned long size;
|
|
FILE *fd;
|
|
char *path;
|
|
cache_t dir;
|
|
int idfonly;
|
|
};
|
|
|
|
void idf_done(idf_t idf)
|
|
{
|
|
if (!idf)
|
|
return;
|
|
if (idf->path)
|
|
free(idf->path);
|
|
if (idf->dir)
|
|
cache_free(idf->dir);
|
|
if (idf->fd)
|
|
fclose(idf->fd);
|
|
free(idf);
|
|
}
|
|
|
|
void idf_shrink(idf_t idf)
|
|
{
|
|
if (!idf)
|
|
return;
|
|
if (idf->dir)
|
|
cache_shrink(idf->dir);
|
|
}
|
|
|
|
static int read_word(FILE *fd, unsigned long *w)
|
|
{
|
|
unsigned char word[4];
|
|
if (fread(word, 1, 4, fd) != 4)
|
|
return -1;
|
|
*w = (unsigned long)word[0] | ((unsigned long)word[1] << 8) |
|
|
((unsigned long)word[2] << 16) |
|
|
((unsigned long)word[3] << 24);
|
|
return 0;
|
|
}
|
|
|
|
static int write_word(FILE *fd, unsigned long w)
|
|
{
|
|
char word[4];
|
|
|
|
word[0] = w & 0xff;
|
|
word[1] = (w & 0xff00) >> 8;
|
|
word[2] = (w & 0xff0000) >> 16;
|
|
word[3] = (w & 0xff000000) >> 24;
|
|
|
|
if (fwrite(word, 1, 4, fd) != 4)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
int idf_magic(const char *fname)
|
|
{
|
|
char sign[4];
|
|
FILE *fd = fopen(dirpath(fname), "rb");
|
|
if (!fd)
|
|
return 0;
|
|
if (fread(sign, 1, 4, fd) != 4) {
|
|
fclose(fd);
|
|
return 0;
|
|
}
|
|
fclose(fd);
|
|
if (!memcmp(sign, "IDF1", 4))
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
idf_t idf_init(const char *fname)
|
|
{
|
|
char sign[4];
|
|
unsigned long dir_size;
|
|
char *fp = dirpath(fname);
|
|
idf_t idf = malloc(sizeof(struct _idf_t));
|
|
if (!idf)
|
|
return NULL;
|
|
idf->path = strdup(fname);
|
|
if (!idf->path)
|
|
goto err;
|
|
idf->idfonly = 0;
|
|
idf->fd = fopen(fp, "rb");
|
|
idf->dir = cache_init(-1, free_idfd);
|
|
if (!idf->fd || !idf->dir)
|
|
goto err;
|
|
if (fseek(idf->fd, 0, SEEK_END))
|
|
goto err;
|
|
idf->size = ftell(idf->fd);
|
|
if (idf->size < 0)
|
|
goto err;
|
|
if (fseek(idf->fd, 0, SEEK_SET))
|
|
goto err;
|
|
if (fread(sign, 1, 4, idf->fd) != 4)
|
|
goto err;
|
|
if (memcmp(sign, "IDF1", 4))
|
|
goto err;
|
|
if (read_word(idf->fd, &dir_size))
|
|
goto err;
|
|
if (dir_size > idf->size)
|
|
goto err;
|
|
while (dir_size > 0) {
|
|
unsigned long off;
|
|
unsigned long size;
|
|
unsigned char sz;
|
|
char name[256];
|
|
idfd_t *e;
|
|
if (fread(&sz, 1, 1, idf->fd) != 1)
|
|
goto err;
|
|
if (fread(name, 1, sz, idf->fd) != sz)
|
|
goto err;
|
|
name[sz] = 0;
|
|
if (read_word(idf->fd, &off))
|
|
goto err;
|
|
if (read_word(idf->fd, &size))
|
|
goto err;
|
|
e = malloc(sizeof(idfd_t));
|
|
if (!e)
|
|
goto err;
|
|
e->offset = off;
|
|
e->size = size;
|
|
e->idf = idf;
|
|
INIT_LIST_HEAD(&e->list);
|
|
if (cache_add(idf->dir, name, e)) {
|
|
free(e);
|
|
goto err;
|
|
}
|
|
cache_forget(idf->dir, e); /* use like hash */
|
|
// fprintf(stderr,"Parsed: '%s' @ %ld, %ld\n", name, off, size);
|
|
dir_size -= (1 + sz + 4 + 4);
|
|
}
|
|
return idf;
|
|
err:
|
|
idf_done(idf);
|
|
return NULL;
|
|
}
|
|
|
|
typedef struct {
|
|
struct list_head list;
|
|
char *path;
|
|
long size;
|
|
} idf_item_t;
|
|
|
|
static int fcopy(FILE *to, const char *fname)
|
|
{
|
|
int rc = -1;
|
|
char buff[4096];
|
|
FILE *fd;
|
|
fd = fopen(fname, "rb");
|
|
if (!fd)
|
|
return -1;
|
|
while (!feof(fd)) {
|
|
int s = fread(buff, 1, sizeof(buff), fd);
|
|
if (!s) {
|
|
if (feof(fd))
|
|
break;
|
|
goto err;
|
|
}
|
|
if (fwrite(buff, 1, s, to) != s)
|
|
goto err;
|
|
}
|
|
rc = 0;
|
|
err:
|
|
fclose(fd);
|
|
return rc;
|
|
}
|
|
|
|
static int idf_tree(const char *path, struct list_head *list, const char *fname)
|
|
{
|
|
DIR *d;
|
|
struct dirent *de;
|
|
if (!path)
|
|
return 0;
|
|
d = opendir(dirpath(path));
|
|
if (!d) {
|
|
if (!access(dirpath(path), R_OK) && fname) {
|
|
FILE *fd; idf_item_t *i;
|
|
fd = fopen(dirpath(path), "rb");
|
|
i = malloc(sizeof(idf_item_t));
|
|
if (!i)
|
|
return -1;
|
|
INIT_LIST_HEAD(&i->list);
|
|
if (!(i->path = strdup(fname)))
|
|
goto err;
|
|
if (fseek(fd, 0, SEEK_END) < 0)
|
|
goto err;
|
|
if ((i->size = ftell(fd)) < 0)
|
|
goto err;
|
|
fclose(fd);
|
|
fprintf(stderr, "Added file: '%s' size: %ld\n", path, i->size);
|
|
list_add(&i->list, list);
|
|
return 0;
|
|
err:
|
|
if (i->path)
|
|
free(i->path);
|
|
free(i);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
while ((de = readdir(d))) {
|
|
char *p;
|
|
if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))
|
|
continue;
|
|
p = getfilepath(path, de->d_name);
|
|
if (p) {
|
|
char *pp = getfilepath(fname, de->d_name);
|
|
if (pp) {
|
|
idf_tree(p, list, pp);
|
|
free(pp);
|
|
}
|
|
free(p);
|
|
}
|
|
}
|
|
closedir(d);
|
|
return 0;
|
|
}
|
|
|
|
int idf_create(const char *file, const char *path)
|
|
{
|
|
int rc = -1, i;
|
|
FILE *fd;
|
|
char *p;
|
|
unsigned long off = 0;
|
|
long dict_size = 0;
|
|
struct list_head *pos;
|
|
|
|
LIST_HEAD(items);
|
|
p = strdup(path);
|
|
if (!p)
|
|
return -1;
|
|
|
|
unix_path(p);
|
|
|
|
i = strlen(p) - 1;
|
|
|
|
while (i >= 0 && p[i] == '/') {
|
|
p[i --] = 0;
|
|
}
|
|
|
|
idf_tree(p, &items, NULL);
|
|
|
|
free(p);
|
|
|
|
list_for_each(pos, &items) {
|
|
idf_item_t *it = (idf_item_t *)pos;
|
|
dict_size += (1 + strlen(it->path) + 4 + 4);
|
|
}
|
|
|
|
fd = fopen(dirpath(file), "wb");
|
|
fwrite("IDF1", 1, 4, fd);
|
|
write_word(fd, dict_size);
|
|
off = 4 + 4 + dict_size;
|
|
|
|
list_for_each(pos, &items) {
|
|
unsigned char s;
|
|
idf_item_t *it = (idf_item_t *)pos;
|
|
s = strlen(it->path);
|
|
if (fwrite(&s, 1, 1, fd) != 1)
|
|
goto err;
|
|
p = strdup(it->path);
|
|
if (!p)
|
|
goto err;
|
|
tolow(p); /* in idf always lowcase */
|
|
if (fwrite(p, 1, s, fd) != s) {
|
|
free(p);
|
|
goto err;
|
|
}
|
|
free(p);
|
|
if (write_word(fd, off) < 0)
|
|
goto err;
|
|
if (write_word(fd, it->size) < 0)
|
|
goto err;
|
|
off += it->size;
|
|
}
|
|
|
|
list_for_each(pos, &items) {
|
|
idf_item_t *it = (idf_item_t *)pos;
|
|
char *p;
|
|
p = getfilepath(path, it->path);
|
|
if (p) {
|
|
int rc = fcopy(fd, p);
|
|
free(p);
|
|
if (rc) {
|
|
fprintf(stderr, "Error while copy file '%s'...\n", it->path);
|
|
goto err;
|
|
}
|
|
}
|
|
}
|
|
rc = 0;
|
|
err:
|
|
if (rc)
|
|
fprintf(stderr, "Error creating idf file...\n");
|
|
|
|
while (!list_empty(&items)) {
|
|
idf_item_t *it = (idf_item_t *)items.next;
|
|
free(it->path);
|
|
list_del(&it->list);
|
|
free(it);
|
|
}
|
|
fclose(fd);
|
|
return rc;
|
|
}
|
|
|
|
|
|
int idf_seek(idff_t fil, int offset, int whence)
|
|
{
|
|
idfd_t *dir = fil->dir;
|
|
switch (whence) {
|
|
case SEEK_SET:
|
|
if (offset < 0 || offset > dir->size) {
|
|
return -1;
|
|
}
|
|
fil->pos = offset;
|
|
break;
|
|
case SEEK_END:
|
|
if (dir->size + offset > dir->size || dir->size + offset < 0) {
|
|
return -1;
|
|
}
|
|
fil->pos = dir->size + offset;
|
|
break;
|
|
case SEEK_CUR:
|
|
if (fil->pos + offset > dir->size || fil->pos + offset < 0) {
|
|
return -1;
|
|
}
|
|
fil->pos += offset;
|
|
break;
|
|
}
|
|
if (!fseek(fil->fd, fil->pos + dir->offset, SEEK_SET))
|
|
return fil->pos;
|
|
return -1;
|
|
}
|
|
|
|
static int idfrw_seek(struct SDL_RWops *context, int offset, int whence)
|
|
{
|
|
idff_t fil = (idff_t)context->hidden.unknown.data1;
|
|
return idf_seek(fil, offset, whence);
|
|
}
|
|
|
|
int idf_read(idff_t fil, void *ptr, int size, int maxnum)
|
|
{
|
|
int rc = 0;
|
|
long pos;
|
|
|
|
idfd_t *dir = fil->dir;
|
|
|
|
if (fseek(fil->fd, dir->offset + fil->pos, SEEK_SET) < 0) {
|
|
return 0;
|
|
}
|
|
#if 1
|
|
while (maxnum) {
|
|
pos = ftell(fil->fd);
|
|
fil->pos = pos - dir->offset;
|
|
|
|
if (fil->pos + size > dir->size) {
|
|
break;
|
|
}
|
|
|
|
if (fread(ptr, size, 1, fil->fd) != 1)
|
|
break;
|
|
// fil->pos += size;
|
|
ptr += size;
|
|
maxnum --;
|
|
rc ++;
|
|
}
|
|
#else
|
|
rc = fread(ptr, size, maxnum, fil->fd);
|
|
#endif
|
|
pos = ftell(fil->fd);
|
|
fil->pos = pos - dir->offset;
|
|
return rc;
|
|
}
|
|
|
|
static int idfrw_read(struct SDL_RWops *context, void *ptr, int size, int maxnum)
|
|
{
|
|
idff_t fil = (idff_t)context->hidden.unknown.data1;
|
|
return idf_read(fil, ptr, size, maxnum);
|
|
}
|
|
|
|
int idf_close(idff_t fil)
|
|
{
|
|
if (fil) {
|
|
fclose(fil->fd);
|
|
list_del(&fil->list);
|
|
free(fil);
|
|
}
|
|
return 0; /* nothing todo */
|
|
}
|
|
|
|
static int idfrw_close(struct SDL_RWops *context)
|
|
{
|
|
if (context) {
|
|
idff_t fil = (idff_t)context->hidden.unknown.data1;
|
|
idf_close(fil);
|
|
SDL_FreeRW(context);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#if 0
|
|
int idf_extract(idf_t idf, const char *fname)
|
|
{
|
|
FILE *out;
|
|
int size;
|
|
idfd_t *dir = NULL;
|
|
char buff[4096];
|
|
if (idf)
|
|
dir = cache_lookup(idf->dir, fname);
|
|
if (!dir)
|
|
return -1;
|
|
fseek(idf->fd, dir->offset, SEEK_SET);
|
|
out = fopen("out.bin", "wb");
|
|
size = dir->size;
|
|
while (size>0) {
|
|
int s ;
|
|
if (size < sizeof(buff))
|
|
s = fread(buff, 1, size, idf->fd);
|
|
else
|
|
s = fread(buff, 1, sizeof(buff), idf->fd);
|
|
fwrite(buff, 1, s, out);
|
|
size -= s;
|
|
fprintf(stderr, "size = %d\n", size);
|
|
}
|
|
fclose(out);
|
|
return 0;
|
|
}
|
|
#endif
|
|
int idf_eof(idff_t idf)
|
|
{
|
|
if (!idf)
|
|
return 1;
|
|
if (idf->pos >= idf->dir->size)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
int idf_error(idff_t idf)
|
|
{
|
|
if (!idf || !idf->fd)
|
|
return -1;
|
|
return ferror(idf->fd);
|
|
}
|
|
|
|
int idf_only(idf_t idf, int fl)
|
|
{ int i;
|
|
if (!idf)
|
|
return -1;
|
|
if (fl == -1)
|
|
return idf->idfonly;
|
|
i = idf->idfonly;
|
|
idf->idfonly = fl;
|
|
return i;
|
|
}
|
|
|
|
idff_t idf_open(idf_t idf, const char *fname)
|
|
{
|
|
idfd_t *dir = NULL;
|
|
idff_t fil = NULL;
|
|
char *p;
|
|
if (!idf || !fname)
|
|
return NULL;
|
|
p = strdup(fname);
|
|
if (!p)
|
|
return NULL;
|
|
tolow(p);
|
|
if (idf)
|
|
dir = cache_lookup(idf->dir, p);
|
|
free(p);
|
|
if (!dir)
|
|
return NULL;
|
|
|
|
fil = malloc(sizeof(struct _idff_t));
|
|
if (!fil)
|
|
return NULL;
|
|
INIT_LIST_HEAD(&fil->list);
|
|
|
|
fil->dir = dir;
|
|
fil->pos = 0;
|
|
fil->fd = fopen(dirpath(idf->path), "rb");
|
|
if (!fil->fd)
|
|
goto err;
|
|
list_add(&fil->list, &dir->list);
|
|
return fil;
|
|
err:
|
|
free(fil);
|
|
return NULL;
|
|
}
|
|
|
|
int idf_access(idf_t idf, const char *fname)
|
|
{
|
|
idfd_t *dir = NULL;
|
|
if (idf)
|
|
dir = cache_lookup(idf->dir, fname);
|
|
if (!dir)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
char *idf_gets(idff_t idf, char *b, int size)
|
|
{
|
|
int rc, rc2;
|
|
if (!idf)
|
|
return NULL;
|
|
if (!size)
|
|
return NULL;
|
|
rc = idf_read(idf, b, 1, size);
|
|
if (rc < 0)
|
|
return NULL;
|
|
if (!rc && idf_eof(idf))
|
|
return NULL;
|
|
if (!idf_eof(idf))
|
|
b[rc - 1] = 0;
|
|
else {
|
|
if (rc < size)
|
|
b[rc] = 0;
|
|
else
|
|
b[size - 1] = 0;
|
|
}
|
|
rc2 = strcspn(b, "\n");
|
|
b[rc2] = 0;
|
|
idf_seek(idf, - (rc - rc2 - 1), SEEK_CUR);
|
|
return b;
|
|
}
|
|
|
|
SDL_RWops *RWFromIdf(idf_t idf, const char *fname)
|
|
{
|
|
idff_t fil = NULL;
|
|
SDL_RWops *n;
|
|
fil = idf_open(idf, fname);
|
|
if (!fil) {
|
|
if (!idf || !idf->idfonly)
|
|
return SDL_RWFromFile(dirpath(fname), "rb");
|
|
return NULL;
|
|
}
|
|
n = SDL_AllocRW();
|
|
if (!n)
|
|
goto err;
|
|
n->seek = idfrw_seek;
|
|
n->read = idfrw_read;
|
|
n->close = idfrw_close;
|
|
n->hidden.unknown.data1 = fil;
|
|
return n;
|
|
err:
|
|
if (n)
|
|
SDL_FreeRW(n);
|
|
free(fil);
|
|
return NULL;
|
|
}
|