X-Git-Url: https://git.draconx.ca/gitweb/upkg.git/blobdiff_plain/03072b0891e7d96690a6299864f6a738ae54038f..5fca0fe6cdbe90d31cce2ef17c1d76a8cfe0f921:/src/libupkg.c diff --git a/src/libupkg.c b/src/libupkg.c index 0dc2ade..3ecce7f 100644 --- a/src/libupkg.c +++ b/src/libupkg.c @@ -1,10 +1,10 @@ /* * upkg: tool for manipulating Unreal Tournament packages. - * Copyright (C) 2009 Nick Bowler + * Copyright © 2009-2011 Nick Bowler * - * This program is free software; you can redistribute it and/or modify + * 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 + * 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, @@ -13,8 +13,7 @@ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * along with this program. If not, see . */ #include @@ -26,16 +25,28 @@ #define MIN(a, b) ((a) < (b) ? (a) : (b)) +/* + * Print a message and execute some statement(s) if the expression evaluates + * to zero. Intended to help verify that assumed constraints on the file + * format actually are not violated. + */ +#define format_assert(expr, body) do { \ + if (!(expr)) { \ + fprintf(stderr, "%s: %d: %s: format assertion failed: %s\n", \ + __FILE__, __LINE__, __func__, #expr); \ + body; \ + } \ +} while (0) + struct upkg_name { unsigned long flags; char *name; }; -struct upkg_export { - const char *name; +struct upkg_export_priv { + struct upkg_export pub; - long package, class, super; - unsigned long flags; + long class, super; unsigned long size, offset; }; @@ -44,19 +55,56 @@ struct upkg_import { long package; }; -struct upkg_private { - FILE *f; +struct upkg_priv { + struct upkg pub; + + const struct upkg_file_ops *fops; + int (*dtor)(void *handle); + void *f; struct upkg_file *last_file; - struct upkg_name *names; - struct upkg_export *exports; - struct upkg_import *imports; + struct upkg_name *names; + struct upkg_export_priv *exports; + struct upkg_import *imports; unsigned long name_offset, export_offset, import_offset; unsigned char guid[16]; }; +/* Default I/O operations for ordinary files. */ +static size_t file_read(void *buf, size_t size, void *handle) +{ + return fread(buf, 1, size, (FILE *)handle); +} + +static int file_seek(void *handle, long offset, int whence) +{ + return fseek((FILE *)handle, offset, whence); +} + +static long file_tell(void *handle) +{ + return ftell((FILE *)handle); +} + +static int file_eof(void *handle) +{ + return feof((FILE *)handle); +} + +static int file_close(void *handle) +{ + return fclose((FILE *)handle); +} + +const struct upkg_file_ops upkg_default_fops = { + .read = file_read, + .seek = file_seek, + .tell = file_tell, + .eof = file_eof, +}; + /* * Decode the compact index format from the upkg. This format is fucked. * Stores the result in *val and returns the number of input bytes read (or 0 @@ -64,10 +112,9 @@ struct upkg_private { */ size_t upkg_decode_index(long *val, unsigned char *bytes, size_t n) { - size_t i = 0; - *val = 0; - while (i < MIN(n, 5)) { + + for (size_t i = 0; i < MIN(n, 5); i++) { /* * Least significant bytes are first, so we need to do this * nonsense. @@ -79,76 +126,99 @@ size_t upkg_decode_index(long *val, unsigned char *bytes, size_t n) *val += tmp; if (!(bytes[i] & (i == 0 ? 0x40 : 0x80))) { - i++; - break; + if (bytes[0] & 0x80) + *val = -*val; + return i+1; } - - i++; } - if (i > MIN(n, 5) || n == 0) - return 0; - if (bytes[0] & 0x80) - *val = -*val; - return i; + /* Error */ + return 0; } -static struct upkg *init_upkg(unsigned char hdr[static UPKG_HDR_SIZE]) +static struct upkg_priv *init_upkg(unsigned char hdr[static UPKG_HDR_SIZE]) { - struct { - struct upkg pkg; - struct upkg_private priv; - } *alloc; + struct upkg_priv *pkg; - alloc = malloc(sizeof *alloc); - if (!alloc) { + pkg = malloc(sizeof *pkg); + if (!pkg) return NULL; - } - alloc->pkg = (struct upkg) { - .version = unpack_16_le(hdr+4), - .license = unpack_16_le(hdr+6), - .flags = unpack_32_le(hdr+8), - .name_count = unpack_32_le(hdr+12), - .export_count = unpack_32_le(hdr+20), - .import_count = unpack_32_le(hdr+28), - .priv = &alloc->priv, - }; + *pkg = (struct upkg_priv) { + .pub = { + .version = unpack_16_le(hdr+4), + .license = unpack_16_le(hdr+6), + .flags = unpack_32_le(hdr+8), + .name_count = unpack_32_le(hdr+12), + .export_count = unpack_32_le(hdr+20), + .import_count = unpack_32_le(hdr+28), + }, - alloc->priv = (struct upkg_private) { .name_offset = unpack_32_le(hdr+16), .export_offset = unpack_32_le(hdr+24), .import_offset = unpack_32_le(hdr+32), }; - return &alloc->pkg; + return pkg; +} + +static int pkg_init_guid(struct upkg_priv *pkg) +{ + const struct upkg_file_ops *fops = pkg->fops; + size_t rc; + + if (pkg->pub.version < 68) { + unsigned long heritage_count, heritage_offset; + unsigned char buf[8]; + + rc = fops->read(buf, sizeof buf, pkg->f); + if (rc < 8) + return -1; + + heritage_count = unpack_32_le(buf+0); + heritage_offset = unpack_32_le(buf+4); + + if (heritage_count == 0) + return -1; + if (fops->seek(pkg->f, heritage_offset, SEEK_SET) != 0) + return -1; + } + + rc = fops->read(pkg->pub.guid, 16, pkg->f); + if (rc < 16) + return -1; + + return 0; } -static int pkg_init_names(struct upkg *pkg) +static int pkg_init_names(struct upkg_priv *pkg) { + const struct upkg_file_ops *fops = pkg->fops; + void *f = pkg->f; + size_t rc, len, nbuf = 0; unsigned long index = 0; - char buf[512]; + unsigned char buf[512]; - if (fseek(pkg->priv->f, pkg->priv->name_offset, SEEK_SET) != 0) + if (fops->seek(f, pkg->name_offset, SEEK_SET) != 0) return -1; - pkg->priv->names = malloc(pkg->name_count * sizeof *pkg->priv->names); - if (!pkg->priv->names) + pkg->names = malloc(pkg->pub.name_count * sizeof *pkg->names); + if (!pkg->names) return -1; - while (index < pkg->name_count) { - struct upkg_name *name = &pkg->priv->names[index]; + while (index < pkg->pub.name_count) { + struct upkg_name *name = &pkg->names[index]; /* Read some data into buffer. */ - if (!feof(pkg->priv->f)) { - rc = fread(buf+nbuf, 1, sizeof buf-nbuf, pkg->priv->f); - if (rc == 0) + if (!fops->eof(pkg->f)) { + rc = fops->read(buf+nbuf, sizeof buf-nbuf, f); + if (rc == 0 && nbuf == 0) goto err; nbuf += rc; } - if (pkg->version >= 64) { + if (pkg->pub.version >= 64) { len = buf[0]; if (nbuf <= len + 4 || buf[len]) goto err; @@ -163,7 +233,7 @@ static int pkg_init_names(struct upkg *pkg) memmove(buf, buf+len+1, nbuf); index++; } else { - char *c = memchr(buf, 0, nbuf); + unsigned char *c = memchr(buf, 0, nbuf); if (!c || nbuf <= c - buf + 5) goto err; len = c - buf + 1; @@ -183,32 +253,35 @@ static int pkg_init_names(struct upkg *pkg) return 0; err: for (unsigned i = 0; i < index; i++) - free(pkg->priv->names[i].name); - free(pkg->priv->names); + free(pkg->names[i].name); + free(pkg->names); return -1; } -static int pkg_init_exports(struct upkg *pkg) +static int pkg_init_exports(struct upkg_priv *pkg) { + const struct upkg_file_ops *fops = pkg->fops; + void *f = pkg->f; + size_t rc, len, nbuf = 0; unsigned long index = 0; - char buf[512]; + unsigned char buf[512]; - if (fseek(pkg->priv->f, pkg->priv->export_offset, SEEK_SET) != 0) + if (fops->seek(f, pkg->export_offset, SEEK_SET) != 0) return -1; - pkg->priv->exports = malloc(pkg->export_count * sizeof *pkg->priv->exports); - if (!pkg->priv->exports) + pkg->exports = malloc(pkg->pub.export_count * sizeof *pkg->exports); + if (!pkg->exports) return -1; - while (index < pkg->export_count) { - struct upkg_export *export = &pkg->priv->exports[index]; + while (index < pkg->pub.export_count) { + struct upkg_export_priv *export = &pkg->exports[index]; long tmp; /* Read some data into buffer. */ - if (!feof(pkg->priv->f)) { - rc = fread(buf+nbuf, 1, sizeof buf-nbuf, pkg->priv->f); - if (rc == 0) + if (!fops->eof(pkg->f)) { + rc = fops->read(buf+nbuf, sizeof buf-nbuf, f); + if (rc == 0 && nbuf == 0) goto err; nbuf += rc; } @@ -223,25 +296,27 @@ static int pkg_init_exports(struct upkg *pkg) len += rc; if (nbuf-len < 4) goto err; - export->package = unpack_32_le(buf+len); + export->pub.package = unpack_s32_le(buf+len); len += 4; rc = upkg_decode_index(&tmp, buf+len, nbuf-len); - if (rc == 0 || tmp < 0 || tmp >= pkg->name_count) goto err; - export->name = pkg->priv->names[tmp].name; + if (rc == 0 || tmp < 0 || tmp >= pkg->pub.name_count) goto err; + export->pub.name = pkg->names[tmp].name; len += rc; if (nbuf-len < 4) goto err; - export->flags = unpack_32_le(buf+len); + export->pub.flags = unpack_32_le(buf+len); len += 4; - rc = upkg_decode_index(&export->size, buf+len, nbuf-len); - if (rc == 0) goto err; + rc = upkg_decode_index(&tmp, buf+len, nbuf-len); + if (rc == 0 || tmp < 0) goto err; + export->size = tmp; len += rc; if (export->size) { - rc = upkg_decode_index(&export->offset, buf+len, nbuf-len); - if (rc == 0) goto err; + rc = upkg_decode_index(&tmp, buf+len, nbuf-len); + if (rc == 0 || tmp < 0) goto err; + export->offset = tmp; len += rc; } @@ -252,53 +327,56 @@ static int pkg_init_exports(struct upkg *pkg) return 0; err: - free(pkg->priv->exports); + free(pkg->exports); return -1; } -static int pkg_init_imports(struct upkg *pkg) +static int pkg_init_imports(struct upkg_priv *pkg) { + const struct upkg_file_ops *fops = pkg->fops; + void *f = pkg->f; + size_t rc, len, nbuf = 0; unsigned long index = 0; - char buf[512]; + unsigned char buf[512]; - if (fseek(pkg->priv->f, pkg->priv->import_offset, SEEK_SET) != 0) + if (fops->seek(f, pkg->import_offset, SEEK_SET) != 0) return -1; - pkg->priv->imports = malloc(pkg->import_count * sizeof *pkg->priv->imports); - if (!pkg->priv->imports) + pkg->imports = malloc(pkg->pub.import_count * sizeof *pkg->imports); + if (!pkg->imports) return -1; - while (index < pkg->import_count) { - struct upkg_import *import = &pkg->priv->imports[index]; + while (index < pkg->pub.import_count) { + struct upkg_import *import = &pkg->imports[index]; long tmp; /* Read some data into buffer. */ - if (!feof(pkg->priv->f)) { - rc = fread(buf+nbuf, 1, sizeof buf-nbuf, pkg->priv->f); - if (rc == 0) + if (!fops->eof(pkg->f)) { + rc = fops->read(buf+nbuf, sizeof buf-nbuf, f); + if (rc == 0 && nbuf == 0) goto err; nbuf += rc; } len = 0; rc = upkg_decode_index(&tmp, buf+len, nbuf-len); - if (rc == 0 || len < 0 || len >= pkg->name_count) goto err; - import->class_package = pkg->priv->names[tmp].name; + if (rc == 0 || len >= pkg->pub.name_count) goto err; + import->class_package = pkg->names[tmp].name; len += rc; rc = upkg_decode_index(&tmp, buf+len, nbuf-len); - if (rc == 0 || len < 0 || len >= pkg->name_count) goto err; - import->class_name = pkg->priv->names[tmp].name; + if (rc == 0 || len >= pkg->pub.name_count) goto err; + import->class_name = pkg->names[tmp].name; len += rc; if (nbuf-len < 4) goto err; - import->package = unpack_32_le(buf+len); + import->package = unpack_s32_le(buf+len); len += 4; rc = upkg_decode_index(&tmp, buf+len, nbuf-len); - if (rc == 0 || len < 0 || len >= pkg->name_count) goto err; - import->object_name = pkg->priv->names[tmp].name; + if (rc == 0 || len >= pkg->pub.name_count) goto err; + import->object_name = pkg->names[tmp].name; len += rc; nbuf -= len; @@ -308,91 +386,192 @@ static int pkg_init_imports(struct upkg *pkg) return 0; err: - free(pkg->priv->imports); + free(pkg->imports); return -1; } -struct upkg *upkg_fopen(const char *path) +struct upkg *upkg_open(void *f, const struct upkg_file_ops *fops, + int (*destructor)(void *handle)) { unsigned char hdr_buf[UPKG_HDR_SIZE]; - struct upkg *pkg; - FILE *f; + struct upkg_priv *pkg; - if (!(f = fopen(path, "rb"))) { + if (fops->read(hdr_buf, sizeof hdr_buf, f) != sizeof hdr_buf) { return NULL; } - if (fread(hdr_buf, sizeof hdr_buf, 1, f) != 1) { - goto err1; - } if (unpack_32_le(hdr_buf) != UPKG_HDR_MAGIC) { - goto err1; + return NULL; } /* Initialize package structure. */ pkg = init_upkg(hdr_buf); if (!pkg) { + return NULL; + } + pkg->fops = fops; + pkg->dtor = destructor; + pkg->f = f; + + if (pkg_init_guid(pkg) != 0) { goto err1; } - pkg->priv->f = f; if (pkg_init_names(pkg) != 0) { - goto err2; + goto err1; } if (pkg_init_exports(pkg) != 0) { - goto err3; + goto err2; } if (pkg_init_imports(pkg) != 0) { - goto err4; + goto err3; } - return pkg; -err4: - free(pkg->priv->exports); + return &pkg->pub; err3: - for (unsigned i = 0; i < pkg->name_count; i++) - free(pkg->priv->names[i].name); - free(pkg->priv->names); + free(pkg->exports); err2: - free(pkg); + for (unsigned i = 0; i < pkg->pub.name_count; i++) + free(pkg->names[i].name); + free(pkg->names); err1: - fclose(f); + free(pkg); return NULL; } -int upkg_close(struct upkg *pkg) +struct upkg *upkg_fopen(const char *path) +{ + struct upkg *pkg; + FILE *f; + + f = fopen(path, "rb"); + if (!f) { + return NULL; + } + + pkg = upkg_open(f, &upkg_default_fops, file_close); + if (!pkg) { + fclose(f); + } + + return pkg; +} + +int upkg_close(struct upkg *pub) { + struct upkg_priv *pkg = (struct upkg_priv *)pub; int rc = 0; - if (pkg->priv->f) { - rc = fclose(pkg->priv->f); + if (pkg->dtor) { + rc = pkg->dtor(pkg->f); + } - for (unsigned i = 0; i < pkg->name_count; i++) { - free(pkg->priv->names[i].name); - } + for (unsigned i = 0; i < pkg->pub.name_count; i++) { + free(pkg->names[i].name); } - free(pkg->priv->imports); - free(pkg->priv->exports); - free(pkg->priv->names); + free(pkg->imports); + free(pkg->exports); + free(pkg->names); free(pkg); return rc; } -const char *upkg_get_name(struct upkg *pkg, unsigned long idx) +const char *upkg_get_name(struct upkg *pub, unsigned long idx) { - if (idx >= pkg->name_count) + struct upkg_priv *pkg = (struct upkg_priv *)pub; + + if (idx >= pkg->pub.name_count) return 0; - return pkg->priv->names[idx].name; + return pkg->names[idx].name; +} + +long upkg_export_find(struct upkg *pub, long parent, const char *name) +{ + struct upkg_priv *pkg = (struct upkg_priv *)pub; + + /* This only makes sense if the assertion below is not violated. */ + long package = parent < 0 ? 0 : parent + 1; + + for (unsigned long i = 0; i < pkg->pub.export_count; i++) { + struct upkg_export_priv *e = &pkg->exports[i]; + + /* Assertion: an object's package is an export. */ + format_assert(e->pub.package >= 0, continue); + if (e->pub.package == package + && strcmp(e->pub.name, name) == 0) { + return i; + } + } + + return -1; +} + +const struct upkg_export *upkg_get_export(struct upkg *pub, unsigned long idx) +{ + struct upkg_priv *pkg = (struct upkg_priv *)pub; + + if (idx < pkg->pub.export_count) + return &pkg->exports[idx].pub; + return NULL; +} + +const char *upkg_export_class(struct upkg *pub, unsigned long idx, + const char **package) +{ + struct upkg_priv *pkg = (struct upkg_priv *)pub; + struct upkg_export_priv *export; + struct upkg_import *iclass, *ipackage; + unsigned long pkg_idx; + + if (idx >= pkg->pub.export_count) + return NULL; + + export = &pkg->exports[idx]; + + /* Assumption: class references are always imports. */ + format_assert(export->class <= 0, return NULL); + + /* Get the class. */ + if (export->class == 0) { + if (package) *package = "Core"; + return "Class"; + } + + pkg_idx = -(export->class + 1); + if (pkg_idx >= pkg->pub.import_count) + return NULL; + iclass = &pkg->imports[pkg_idx]; + + /* Assumption: class references are always Core.Class. */ + format_assert(!strcmp(iclass->class_package, "Core"), return NULL); + format_assert(!strcmp(iclass->class_name, "Class"), return NULL); + + /* Assumption: package references are always imports. */ + format_assert(iclass->package <= 0, return NULL); + + /* Get the package. */ + pkg_idx = -(iclass->package + 1); + if (pkg_idx >= pkg->pub.import_count) + return NULL; + ipackage = &pkg->imports[pkg_idx]; + + /* Assumption: package references are always Core.Package. */ + format_assert(!strcmp(ipackage->class_package, "Core"), return NULL); + format_assert(!strcmp(ipackage->class_name, "Package"), return NULL); + + if (package) *package = ipackage->object_name; + return iclass->object_name; } -struct upkg_file *upkg_export_open(struct upkg *pkg, unsigned long idx) +struct upkg_file *upkg_export_open(struct upkg *pub, unsigned long idx) { + struct upkg_priv *pkg = (struct upkg_priv *)pub; struct upkg_file *f; - if (idx >= pkg->export_count) + if (idx >= pkg->pub.export_count) return NULL; f = malloc(sizeof *f); @@ -401,8 +580,9 @@ struct upkg_file *upkg_export_open(struct upkg *pkg, unsigned long idx) *f = (struct upkg_file) { .pkg = pkg, - .base = pkg->priv->exports[idx].offset, - .len = pkg->priv->exports[idx].size, + .base = pkg->exports[idx].offset, + .len = pkg->exports[idx].size, + .name = pkg->exports[idx].pub.name, }; return f; @@ -410,13 +590,52 @@ struct upkg_file *upkg_export_open(struct upkg *pkg, unsigned long idx) void upkg_export_close(struct upkg_file *f) { - if (f->pkg->priv->last_file == f) - f->pkg->priv->last_file = NULL; + if (f->pkg->last_file == f) + f->pkg->last_file = NULL; free(f); } +long upkg_export_tell(struct upkg_file *f) +{ + return f->offset; +} + +int upkg_export_seek(struct upkg_file *f, long offset, int whence) +{ + const struct upkg_file_ops *fops = f->pkg->fops; + int rc = EOF; + + switch (whence) { + case SEEK_CUR: + offset = f->offset + offset; + case SEEK_SET: + if (offset < 0 || offset > f->len) + return EOF; + rc = fops->seek(f->pkg->f, f->base + offset, SEEK_SET); + break; + case SEEK_END: + offset = -offset; + if (offset < 0 || offset > f->len) + return EOF; + offset = f->len - offset; + rc = fops->seek(f->pkg->f, f->base + offset, SEEK_SET); + break; + } + + if (rc == 0) { + f->pkg->last_file = f; + f->offset = offset; + f->eof = 0; + } else if (f->pkg->last_file == f) { + f->pkg->last_file = NULL; + } + + return rc; +} + size_t upkg_export_read(struct upkg_file *f, void *buf, size_t n) { + const struct upkg_file_ops *fops = f->pkg->fops; size_t want = MIN(n, f->len - f->offset); size_t rc; @@ -424,12 +643,15 @@ size_t upkg_export_read(struct upkg_file *f, void *buf, size_t n) return 0; } - if (f != f->pkg->priv->last_file) { - if (fseek(f->pkg->priv->f, f->base + f->offset, SEEK_SET)) + if (f != f->pkg->last_file) { + if (fops->seek(f->pkg->f, f->base + f->offset, SEEK_SET)) return 0; } - rc = fread(buf, 1, want, f->pkg->priv->f); + rc = fops->read(buf, want, f->pkg->f); f->offset += rc; + + if (want < n || (rc < want && fops->eof(f->pkg->f))) + f->eof = 1; return rc; }