commit 2562781a7ef04eef3b0184fc7c54ebf247ed2c9f Author: Jesús A. Álvarez Date: Thu Mar 22 12:49:31 2012 +0100 initial commit diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..731b89b --- /dev/null +++ b/Makefile @@ -0,0 +1,16 @@ +LIB = libres.a + +CC = clang +AR = ar +RANLIB = ranlib +CFLAGS = -fPIC -std=c99 + +all: $(LIB) + +$(LIB): res.c + $(CC) -c $(CFLAGS) res.c + $(AR) -ru $(LIB) res.o + $(RANLIB) $(LIB) + +clean: + rm -rf $(LIB) res.o \ No newline at end of file diff --git a/README b/README new file mode 100644 index 0000000..c8cff80 --- /dev/null +++ b/README @@ -0,0 +1,16 @@ +libres - library for reading Macintosh resource forks +Copyright (C) 2008-2009 Jesus A. Alvarez + +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 2 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, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. \ No newline at end of file diff --git a/libres_internal.h b/libres_internal.h new file mode 100644 index 0000000..57bd540 --- /dev/null +++ b/libres_internal.h @@ -0,0 +1,126 @@ +// libres internal header +#include +#include +#include "res.h" + +#define kCompressedResourceTag 0xA89F6572 +#define kCompressedResourceFlg0 0x00120901 +#define kCompressedResourceFlg1 0x00120801 +#define kDCMPInvalidFlags 0xD5DC + +#define efail(n) {errno = n; return NULL;} +#define effail(n, m) {errno = n; free(m); return NULL;} +#define eret(n, r) {errno = n; return r;} +#define egoto(n, l) {errno = n; goto l;} + +// in-memory structures +struct RFILE { + FILE *fp; // file + void *buf; // memory + void *fpriv; // functions + res_seek_func seek; // functions + res_read_func read; // functions + size_t size; + size_t dataOffset; + uint16_t attributes; + size_t numTypes; + struct RmType *types; +}; + +struct RmType { + uint32_t type; + size_t count; + struct RmResRef *list; +}; + +struct RmResRef { + int16_t ID; + RFlags flags; + uint32_t size; // logical size (uncompressed) + uint32_t psize; // physical size + uint32_t offset; // offset from data section + int16_t dcmp; // decompressor ID + char* name; +}; + + +// in-file structures + +struct __attribute__ ((__packed__)) RfHdr { + // resource fork header + uint32_t dataOffset; + uint32_t mapOffset; + uint32_t dataLength; + uint32_t mapLength; +}; + +struct __attribute__ ((__packed__)) RfMap { + // resource map + struct RfHdr headerCopy; + uint32_t rsvNextMapHandle; + uint16_t rsvFileRef; + uint16_t attributes; + uint16_t typeListOffset; // from beginning of map to type list + uint16_t nameListOffset; // from beginning of map to name list + //uint16_t numTypes; // types minus one, overlaps RfTypeList +}; + +struct __attribute__ ((__packed__)) RfTypeEntry { + // resource type entry + uint32_t type; // resource type + uint16_t count; // number of resources minus one + uint16_t offset; // offset to ref list from type list +}; + +struct __attribute__ ((__packed__)) RfTypeList { + // resource type list + uint16_t count; // minus one + struct RfTypeEntry entry[]; +}; + +struct __attribute__ ((__packed__)) RfRefEntry { + // resource reference entry + int16_t ID; + uint16_t nameOffset; + uint8_t attributes; + uint8_t offHi; + uint16_t offLo; + uint32_t rsvHandle; +}; + +struct __attribute__ ((__packed__)) RfResEntry { + // resource data entry + uint32_t length; + char data[]; +}; + +struct __attribute__ ((__packed__)) RfCmpHdr { + // compressed resource entry + uint32_t tag; + uint32_t flags; + uint32_t size; // uncompressed + union __attribute__ ((__packed__)) { + struct __attribute__ ((__packed__)) { + int16_t dcmp; // decompressor ID + uint8_t wrkBufFrSz; // working buffer fractional size + uint8_t expBufSz; // expansion buffer size + } v0; + struct __attribute__ ((__packed__)) { + uint8_t wrkBufFrSz; // working buffer fractional size + uint8_t expBufSz; // expansion buffer size + int16_t dcmp; // decompressor ID + } v1; + } u; +}; + +// private functions +void* res_bread (RFILE *rp, void *buf, size_t offset, size_t count); +uint32_t res_szread (RFILE *rp, size_t offset); +RFILE* res_load (RFILE *rp); +int res_ref_compar (const struct RmResRef *, const struct RmResRef *); +int res_type_compar (const struct RmType *, const struct RmType *); +struct RmType * res_type_find (RFILE *rp, uint32_t type); +struct RmResRef * res_ref_find (RFILE *rp, struct RmType *type, int16_t ID); +struct RmResRef * res_ref_find_named (RFILE *rp, struct RmType *type, const char *name); +int res_ref_name_compar (const struct RmResRef * a, const struct RmResRef * b); +void* res_read_raw (RFILE *rp, struct RmResRef *ref, void *buf, size_t start, size_t size, size_t *read, size_t *remain); diff --git a/res.c b/res.c new file mode 100644 index 0000000..6ca5bdf --- /dev/null +++ b/res.c @@ -0,0 +1,395 @@ +/* + * libres - library for reading Macintosh resource forks + * Copyright (C) 2008-2009 Jesus A. Alvarez + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +// http://developer.apple.com/documentation/mac/MoreToolbox/MoreToolbox-99.html + +#include +#include +#include +#include +#include +#include +#include "res.h" +#include "libres_internal.h" + +const char * libres_id = "libres 1.0.0 (C)2008-2009 namedfork.net"; + +RFILE* res_open (const char *path, int mode) { + if (mode != 0) efail(EINVAL); + RFILE* rp = malloc(sizeof(RFILE)); + if (rp == NULL) efail(ENOMEM); + bzero(rp, sizeof(RFILE)); + + // open + rp->fp = fopen(path, "r"); + if (rp->fp == NULL) { + free(rp); + return NULL; + } + + // get size + errno = 0; + fseek(rp->fp, 0, SEEK_END); + rp->size = ftell(rp->fp); + if (errno) effail(errno, rp); + + rp->buf = NULL; + return res_load(rp); +} + +RFILE* res_open_mem (void *buf, size_t size, int copy) { + RFILE* rp = malloc(sizeof(RFILE)); + if (rp == NULL) efail(ENOMEM); + bzero(rp, sizeof(RFILE)); + rp->size = size; + if (copy) { + rp->buf = malloc(size); + if (rp == NULL) effail(ENOMEM, rp); + memcpy(rp->buf, buf, size); + } else rp->buf = buf; + + return res_load(rp); +} + +RFILE* res_open_funcs (void *priv, res_seek_func seekf, res_read_func readf) { + RFILE* rp = malloc(sizeof(RFILE)); + if (rp == NULL) efail(ENOMEM); + bzero(rp, sizeof(RFILE)); + rp->seek = seekf; + rp->read = readf; + rp->fpriv = priv; + rp->size = rp->seek(priv, 0, SEEK_END); + return res_load(rp); +} + +int res_close (RFILE* rp) { + if (rp == NULL) eret(EBADF, EOF); + if (rp->buf) free(rp->buf); + if (rp->fp) fclose(rp->fp); + + // free list + if (rp->types) { + for(int i=0; i < rp->numTypes; i++) { + struct RmType *t = &rp->types[i]; + if (t->list == NULL) continue; + for(int j=0; j < t->count; j++) { + struct RmResRef *r = &t->list[j]; + if (r->name) free(r->name); + } + free(t->list); + } + free(rp->types); + } + + free(rp); + return 0; +} + +size_t res_typecount (RFILE *rp) { + return rp->numTypes; +} + +uint32_t* res_types (RFILE *rp, uint32_t *buf, size_t start, size_t size, size_t *read, size_t *remain) { + if (buf != NULL && size == 0) efail(EINVAL); + if (size == 0 || start + size > rp->numTypes) size = rp->numTypes - start; + if (buf == NULL) buf = calloc(size, sizeof(uint32_t)); + if (buf == NULL) efail(ENOMEM); + if (read) *read = size; + if (remain) *remain = rp->numTypes - (size + start); + + for(size_t i=0; i < size; i++) + buf[i] = rp->types[start+i].type; + + return buf; +} + +size_t res_count (RFILE *rp, uint32_t type) { + struct RmType *t = res_type_find(rp, type); + if (t == NULL) return 0; + return t->count; +} + +ResAttr* res_list (RFILE *rp, uint32_t type, ResAttr *buf, size_t start, size_t size, size_t *read, size_t *remain) { + struct RmType *t = res_type_find(rp, type); + if (t == NULL) efail(ENOENT); + if (buf != NULL && size == 0) efail(EINVAL); + if (size == 0 || start + size > t->count) size = t->count - start; + if (buf == NULL) buf = calloc(size, sizeof(ResAttr)); + if (buf == NULL) efail(ENOMEM); + if (read) *read = size; + if (remain) *remain = t->count - (size + start); + + for(size_t i=0; i < size; i++) { + buf[i].ID = t->list[start+i].ID; + buf[i].flags = t->list[start+i].flags; + buf[i].size = t->list[start+i].size; + buf[i].name = t->list[start+i].name; + } + + return buf; +} + +ResAttr* res_attr (RFILE *rp, uint32_t type, int16_t ID, ResAttr *buf) { + struct RmType *t = res_type_find(rp, type); + if (t == NULL) efail(ENOENT); + struct RmResRef *ref = res_ref_find(rp, t, ID); + if (ref == NULL) efail(ENOENT); + if (buf == NULL) buf = malloc(sizeof(ResAttr)); + if (buf == NULL) efail(ENOMEM); + + buf->ID = ref->ID; + buf->flags = ref->flags; + buf->size = ref->size; + buf->name = ref->name; + + return buf; +} + +ResAttr* res_attr_named (RFILE *rp, uint32_t type, const char *name, ResAttr *buf) { + struct RmResRef *ref = res_ref_find_named(rp, res_type_find(rp, type), name); + if (ref == NULL) efail(ENOENT); + return res_attr(rp, type, ref->ID, buf); +} + +void* res_read (RFILE *rp, uint32_t type, int16_t ID, void *buf, size_t start, size_t size, size_t *read, size_t *remain) { + struct RmType *t = res_type_find(rp, type); + if (t == NULL) efail(ENOENT); + struct RmResRef *ref = res_ref_find(rp, t, ID); + if (ref == NULL) efail(ENOENT); + + if (ref->flags.fl.compressed) efail(ENOSYS); + return res_read_raw(rp, ref, buf, start, size, read, remain); +} + +void* res_read_named (RFILE *rp, uint32_t type, const char *name, void *buf, size_t start, size_t size, size_t *read, size_t *remain) { + struct RmResRef *ref = res_ref_find_named(rp, res_type_find(rp, type), name); + if (ref == NULL) efail(ENOENT); + return res_read(rp, type, ref->ID, buf, start, size, read, remain); +} + +void* res_read_ind (RFILE *rp, uint32_t type, int16_t ind, void *buf, size_t start, size_t size, size_t *read, size_t *remain) { + struct RmType *t = res_type_find(rp, type); + if (t == NULL) efail(ENOENT); + if (ind >= t->count || ind < 0) efail(ENOENT); + struct RmResRef *ref = &t->list[ind]; + if (ref == NULL) efail(ENOENT); + + if (ref->flags.fl.compressed) efail(ENOSYS); + return res_read_raw(rp, ref, buf, start, size, read, remain); +} + +void res_printdir (RFILE *rp) { + for(int i=0; i < rp->numTypes; i++) { + struct RmType *t = &rp->types[i]; + for(int j=0; j < t->count; j++) + printf("%c%c%c%c %hd (%ub) %s\n", TYPECHARS(t->type), t->list[j].ID, t->list[j].size, t->list[j].name?t->list[j].name:""); + } +} + +void res_printattr (const ResAttr *attr, uint32_t type) { + if (attr == NULL) return; + if (type) + printf("Type: %c%c%c%c\n", TYPECHARS(type)); + printf("ID: %hd\n", attr->ID); + printf("Size: %u\n", attr->size); + if (attr->name) + printf("Name: %s\n", attr->name); + printf("Attr: "); + if (attr->flags.fl.sysRef) printf("sysRef%s", ((attr->flags.b&0x7F)?", ":"")); + if (attr->flags.fl.sysHeap) printf("sysHeap%s", ((attr->flags.b&0x3F)?", ":"")); + if (attr->flags.fl.purgeable) printf("purgeable%s", ((attr->flags.b&0x1F)?", ":"")); + if (attr->flags.fl.locked) printf("locked%s", ((attr->flags.b&0x0F)?", ":"")); + if (attr->flags.fl.protected) printf("protected%s", ((attr->flags.b&0x07)?", ":"")); + if (attr->flags.fl.preload) printf("preload%s", ((attr->flags.b&0x03)?", ":"")); + if (attr->flags.fl.changed) printf("changed%s", ((attr->flags.b&0x01)?", ":"")); + if (attr->flags.fl.compressed) printf("compressed"); + printf("\n\n"); +} + +#if 0 +#pragma mark - +#pragma mark Private Functions +#endif + +void* res_bread (RFILE *rp, void *buf, size_t offset, size_t count) { + if (offset+count > rp->size) efail(EFAULT); + if (buf == NULL) buf = malloc(count); + if (buf == NULL) efail(ENOMEM); + + if (rp->buf) + // memory + memcpy(buf, rp->buf+offset, count); + else if (rp->fp) { + // file pointer + fseek(rp->fp, offset, SEEK_SET); + fread(buf, 1, count, rp->fp); + } else { + // functions + rp->seek(rp->fpriv, (long)offset, (int)SEEK_SET); + rp->read(rp->fpriv, buf, (unsigned long)count); + } + return buf; +} + +uint32_t res_szread (RFILE *rp, size_t offset) { + uint32_t r = 0; + res_bread(rp, &r, offset, sizeof r); + return ntohl(r); +} + +void* res_read_raw (RFILE *rp, struct RmResRef *ref, void *buf, size_t start, size_t size, size_t *read, size_t *remain) { + if (ref == NULL) efail(ENOENT); + if (buf != NULL && size == 0) efail(EINVAL); + if (size == 0 || start + size > ref->psize) size = ref->psize - start; + size_t rstart = start + ref->offset + rp->dataOffset + 4; + if (rstart+size > rp->size) efail(EFAULT); + if (buf == NULL) buf = malloc(ref->psize); + if (buf == NULL) efail(ENOMEM); + if (read) *read = size; + if (remain) *remain = ref->psize - (size + start); + + return res_bread(rp, buf, rstart, size); +} + +RFILE* res_load (RFILE *rp) { + // read header + struct RfHdr hdr; + res_bread(rp, &hdr, 0, sizeof(struct RfHdr)); + rp->dataOffset = ntohl(hdr.dataOffset); + + // read map + struct RfMap *map = res_bread(rp, NULL, (size_t)ntohl(hdr.mapOffset), (size_t)ntohl(hdr.mapLength)); + if (map == NULL) egoto(EINVAL, error); + rp->attributes = ntohs(map->attributes); + struct RfTypeList *types = ((void*)map)+ntohs(map->typeListOffset); + uint8_t *names = ((void*)map)+ntohs(map->nameListOffset); + + // read types + rp->numTypes = 1+ntohs(types->count); + rp->types = calloc(rp->numTypes, sizeof(struct RmType)); + if (rp->types == NULL) egoto(ENOMEM, error); + bzero(rp->types, sizeof(struct RmType) * rp->numTypes); + + for(int i=0; i < rp->numTypes; i++) { + struct RmType *t = &rp->types[i]; + t->type = ntohl(types->entry[i].type); + t->count = 1+(size_t)ntohs(types->entry[i].count); + t->list = calloc(t->count, sizeof(struct RmResRef)); + if (t->list == NULL) egoto(ENOMEM, error); + bzero(t->list, t->count * sizeof(struct RmResRef)); + + // read resource refs & names + int refsNeedSort = 0; + struct RfRefEntry *ent = ((void*)types)+ntohs(types->entry[i].offset); + for(int j=0; j < t->count; j++) { + t->list[j].ID = ntohs(ent[j].ID); + if (j && (t->list[j].ID < t->list[j-1].ID)) refsNeedSort = 1; + t->list[j].flags.b = ent[j].attributes; + t->list[j].offset = ((ent[j].offHi << 16) | ntohs(ent[j].offLo)); + t->list[j].psize = res_szread(rp, rp->dataOffset+t->list[j].offset); + + uint16_t nameOffset = ntohs(ent[j].nameOffset); + if (nameOffset == 0xFFFF) t->list[j].name = NULL; + else { + t->list[j].name = malloc(names[nameOffset]+1); + if (t->list[j].name == NULL) egoto(ENOMEM, error); + t->list[j].name[names[nameOffset]] = '\0'; + memcpy(t->list[j].name, &names[nameOffset+1], names[nameOffset]); + } + + // find logical size + if (t->list[j].flags.fl.compressed) { + struct RfCmpHdr cmpHdr; + if (res_read_raw(rp, &t->list[j], &cmpHdr, 0, sizeof cmpHdr, NULL, NULL) == NULL) + goto notCompressed; + if (ntohl(cmpHdr.tag) != kCompressedResourceTag) + goto notCompressed; + t->list[j].size = ntohl(cmpHdr.size); + + if (ntohl(cmpHdr.flags) == kCompressedResourceFlg0) + t->list[j].dcmp = ntohs(cmpHdr.u.v0.dcmp); + else if (ntohl(cmpHdr.flags) == kCompressedResourceFlg1) + t->list[j].dcmp = ntohs(cmpHdr.u.v1.dcmp); + else { + t->list[j].dcmp = kDCMPInvalidFlags; + fprintf(stderr, "libres: %c%c%c%c %hd: unknown compression flags\n", TYPECHARS(t->type), t->list[j].ID); + } + } + + // resource not compressed, logical size = physical size + if (t->list[j].flags.fl.compressed == 0) { + notCompressed: + t->list[j].flags.fl.compressed = 0; + t->list[j].size = t->list[j].psize; + } + } + + // keep ref list sorted + // they are normally sorted by ID already, so it's rarely needed + if (refsNeedSort) qsort(t->list, t->count, sizeof(struct RmResRef), (int(*)(const void*, const void*))res_ref_compar); + } + + // keep type list sorted + // types are sorted alphabetically in files, we need them sorted numerically + qsort(rp->types, rp->numTypes, sizeof(struct RmType), (int(*)(const void*, const void*))res_type_compar); + + errno = 0; + free(map); + return rp; +error: + free(map); + res_close(rp); + return NULL; +} + +int res_ref_compar (const struct RmResRef * a, const struct RmResRef * b) { + return (int)(a->ID - b->ID); +} + +int res_type_compar (const struct RmType * a, const struct RmType * b) { + // types are unsigned, compare manually + if (a->type == b->type) return 0; + if (a->type < b->type) return -1; + return 1; +} + +struct RmType * res_type_find (RFILE *rp, uint32_t type) { + struct RmType key; + key.type = type; + return bsearch(&key, rp->types, rp->numTypes, sizeof(struct RmType), (int(*)(const void*, const void*))res_type_compar); +} + +struct RmResRef * res_ref_find (RFILE *rp, struct RmType *type, int16_t ID) { + struct RmResRef key; + if (type == NULL) return NULL; + key.ID = ID; + return bsearch(&key, type->list, type->count, sizeof(struct RmResRef), (int(*)(const void*, const void*))res_ref_compar); +} + +struct RmResRef * res_ref_find_named (RFILE *rp, struct RmType *type, const char *name) { + struct RmResRef key; + if (type == NULL) return NULL; + key.name = (char*)name; + size_t count = type->count; + return lfind(&key, type->list, &count, sizeof(struct RmResRef), (int(*)(const void*, const void*))res_ref_name_compar); +} + +int res_ref_name_compar (const struct RmResRef * a, const struct RmResRef * b) { + if (a->name == NULL || b->name == NULL) return 1; + return strcmp(a->name, b->name); +} diff --git a/res.h b/res.h new file mode 100644 index 0000000..f40bc82 --- /dev/null +++ b/res.h @@ -0,0 +1,121 @@ +/* + * libres - library for reading Macintosh resource forks + * Copyright (C) 2008-2009 Jesus A. Alvarez + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef _RES_H_ +#define _RES_H_ + +#include +#include + +#define TYPECHARS(t) ((t) >> 24) & 0xFF, ((t) >> 16) & 0xFF, ((t) >> 8) & 0xFF, (t) & 0xFF + +extern const char * libres_id; + +// in-memory structures +typedef struct RFILE RFILE; + +typedef union __attribute__ ((__packed__)) { + struct { + unsigned int compressed:1; + unsigned int changed:1; + unsigned int preload:1; + unsigned int protected:1; + unsigned int locked:1; + unsigned int purgeable:1; + unsigned int sysHeap:1; + unsigned int sysRef:1; + } fl; + uint8_t b; +} RFlags; + +/* this structure is only valid while the file is open */ +struct ResAttr { + int16_t ID; + RFlags flags; + uint32_t size; + const char* name; // owned by file, MacRoman encoding +}; +typedef struct ResAttr ResAttr; + +typedef unsigned long (*res_seek_func)(void *, long, int); +typedef unsigned long (*res_read_func)(void *, void *, unsigned long); + +/** + @param path path to file + @param mode reserved, set to 0 + @returns reference to open file or NULL + */ +RFILE* res_open (const char *path, int mode); + +/** + @param buf resource buffer + @param size size of buf + @param copy 1 to make a copy of buf, otherwise libres takes ownership of buf and frees it when it's closed + @returns reference to open file or NULL + */ +RFILE* res_open_mem (void *buf, size_t size, int copy); +RFILE* res_open_funcs (void *priv, res_seek_func seek, res_read_func read); +int res_close (RFILE *rp); + +/// number of resource types +size_t res_typecount (RFILE *rp); + +/** + List resource types in the file + @param buf list output or NULL + @param start start element or 0 + @param size number of elements to list or 0 + @param read returns number of resources read, if not NULL + @param remain number of resources left unread, if not NULL + @returns buf (filled), or newly allocated complete list + */ +uint32_t* res_types (RFILE *rp, uint32_t *buf, size_t start, size_t size, size_t *read, size_t *remain); + +/// count resources of a type +size_t res_count (RFILE *rp, uint32_t type); + +/** + List resource attributes + @param buf list output or NULL + @param start start element or 0 + @param size number of elements to list or 0 + @param read returns number of resources read, if not NULL + @param remain number of resources left unread, if not NULL + @returns buf (filled), or newly allocated complete list + */ +ResAttr* res_list (RFILE *rp, uint32_t type, ResAttr *buf, size_t start, size_t size, size_t *read, size_t *remain); + +/// get attributes for a resource (by ID) +ResAttr* res_attr (RFILE *rp, uint32_t type, int16_t ID, ResAttr *buf); + +/// get attributes for a resource (by name) +ResAttr* res_attr_named (RFILE *rp, uint32_t type, const char *name, ResAttr *buf); + +/** + Read a resource + @param buf resource output or NULL + @param size size of buffer, ignored if buf is NULL + @returns buf (filled), or newly allocated resource + */ +void* res_read (RFILE *rp, uint32_t type, int16_t ID, void *buf, size_t start, size_t size, size_t *read, size_t *remain); +void* res_read_named (RFILE *rp, uint32_t type, const char *name, void *buf, size_t start, size_t size, size_t *read, size_t *remain); + +void res_printdir (RFILE *rp); +void res_printattr (const ResAttr *attr, uint32_t type); +#endif /* _RES_H_ */