/* * upkg: tool for manipulating Unreal Tournament packages. * Copyright (C) 2009 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 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include "upkg.h" #include "pack.h" #define MIN(a, b) ((a) < (b) ? (a) : (b)) struct upkg_name { unsigned long flags; char *name; }; struct upkg_export { const char *name; long package, class, super; unsigned long flags; unsigned long size, offset; }; struct upkg_import { const char *class_package, *class_name, *object_name; long package; }; struct upkg_private { FILE *f; struct upkg_file *last_file; struct upkg_name *names; struct upkg_export *exports; struct upkg_import *imports; unsigned long name_offset, export_offset, import_offset; unsigned char guid[16]; }; /* * 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 * if the input is invalid, in which case *val is undefined). */ size_t upkg_decode_index(long *val, unsigned char *bytes, size_t n) { size_t i = 0; *val = 0; while (i < MIN(n, 5)) { /* * Least significant bytes are first, so we need to do this * nonsense. */ long tmp = bytes[i] & (i == 0 ? 0x3f : 0x7f); if (i > 0) tmp <<= 6; if (i > 1) tmp <<= 7*(i-1); *val += tmp; if (!(bytes[i] & (i == 0 ? 0x40 : 0x80))) { i++; break; } i++; } if (i > MIN(n, 5) || n == 0) return 0; if (bytes[0] & 0x80) *val = -*val; return i; } static struct upkg *init_upkg(unsigned char hdr[static UPKG_HDR_SIZE]) { struct { struct upkg pkg; struct upkg_private priv; } *alloc; alloc = malloc(sizeof *alloc); if (!alloc) { 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, }; 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; } static int pkg_init_guid(struct upkg *pkg) { size_t rc; if (pkg->version < 68) { unsigned long heritage_count, heritage_offset; unsigned char buf[8]; rc = fread(buf, 1, sizeof buf, pkg->priv->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 (fseek(pkg->priv->f, heritage_offset, SEEK_SET) != 0) return -1; } rc = fread(pkg->guid, 1, 16, pkg->priv->f); if (rc < 16) return -1; return 0; } static int pkg_init_names(struct upkg *pkg) { size_t rc, len, nbuf = 0; unsigned long index = 0; char buf[512]; if (fseek(pkg->priv->f, pkg->priv->name_offset, SEEK_SET) != 0) return -1; pkg->priv->names = malloc(pkg->name_count * sizeof *pkg->priv->names); if (!pkg->priv->names) return -1; while (index < pkg->name_count) { struct upkg_name *name = &pkg->priv->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) goto err; nbuf += rc; } if (pkg->version >= 64) { len = buf[0]; if (nbuf <= len + 4 || buf[len]) goto err; name->name = malloc(len); if (!name->name) goto err; memcpy(name->name, buf+1, len); name->flags = unpack_32_le(buf+len+1); len += 4; nbuf -= len + 1; memmove(buf, buf+len+1, nbuf); index++; } else { char *c = memchr(buf, 0, nbuf); if (!c || nbuf <= c - buf + 5) goto err; len = c - buf + 1; name->name = malloc(len); if (!name->name) goto err; memcpy(name->name, buf, len); name->flags = unpack_32_le(buf+len); len += 4; nbuf -= len; memmove(buf, buf+len, nbuf); index++; } } return 0; err: for (unsigned i = 0; i < index; i++) free(pkg->priv->names[i].name); free(pkg->priv->names); return -1; } static int pkg_init_exports(struct upkg *pkg) { size_t rc, len, nbuf = 0; unsigned long index = 0; char buf[512]; if (fseek(pkg->priv->f, pkg->priv->export_offset, SEEK_SET) != 0) return -1; pkg->priv->exports = malloc(pkg->export_count * sizeof *pkg->priv->exports); if (!pkg->priv->exports) return -1; while (index < pkg->export_count) { struct upkg_export *export = &pkg->priv->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) goto err; nbuf += rc; } len = 0; rc = upkg_decode_index(&export->class, buf+len, nbuf-len); if (rc == 0) goto err; len += rc; rc = upkg_decode_index(&export->super, buf+len, nbuf-len); if (rc == 0) goto err; len += rc; if (nbuf-len < 4) goto err; export->package = unpack_32_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; len += rc; if (nbuf-len < 4) goto err; export->flags = unpack_32_le(buf+len); len += 4; rc = upkg_decode_index(&export->size, buf+len, nbuf-len); if (rc == 0) goto err; len += rc; if (export->size) { rc = upkg_decode_index(&export->offset, buf+len, nbuf-len); if (rc == 0) goto err; len += rc; } nbuf -= len; memmove(buf, buf+len, nbuf); index++; } return 0; err: free(pkg->priv->exports); return -1; } static int pkg_init_imports(struct upkg *pkg) { size_t rc, len, nbuf = 0; unsigned long index = 0; char buf[512]; if (fseek(pkg->priv->f, pkg->priv->import_offset, SEEK_SET) != 0) return -1; pkg->priv->imports = malloc(pkg->import_count * sizeof *pkg->priv->imports); if (!pkg->priv->imports) return -1; while (index < pkg->import_count) { struct upkg_import *import = &pkg->priv->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) 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; 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; len += rc; if (nbuf-len < 4) goto err; import->package = unpack_32_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; len += rc; nbuf -= len; memmove(buf, buf+len, nbuf); index++; } return 0; err: free(pkg->priv->imports); return -1; } struct upkg *upkg_fopen(const char *path) { unsigned char hdr_buf[UPKG_HDR_SIZE]; struct upkg *pkg; FILE *f; if (!(f = fopen(path, "rb"))) { 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; } /* Initialize package structure. */ pkg = init_upkg(hdr_buf); if (!pkg) { goto err1; } pkg->priv->f = f; if (pkg_init_guid(pkg) != 0) { goto err2; } if (pkg_init_names(pkg) != 0) { goto err2; } if (pkg_init_exports(pkg) != 0) { goto err3; } if (pkg_init_imports(pkg) != 0) { goto err4; } return pkg; err4: free(pkg->priv->exports); err3: for (unsigned i = 0; i < pkg->name_count; i++) free(pkg->priv->names[i].name); free(pkg->priv->names); err2: free(pkg); err1: fclose(f); return NULL; } int upkg_close(struct upkg *pkg) { int rc = 0; if (pkg->priv->f) { rc = fclose(pkg->priv->f); for (unsigned i = 0; i < pkg->name_count; i++) { free(pkg->priv->names[i].name); } } free(pkg->priv->imports); free(pkg->priv->exports); free(pkg->priv->names); free(pkg); return rc; } const char *upkg_get_name(struct upkg *pkg, unsigned long idx) { if (idx >= pkg->name_count) return 0; return pkg->priv->names[idx].name; } struct upkg_file *upkg_export_open(struct upkg *pkg, unsigned long idx) { struct upkg_file *f; if (idx >= pkg->export_count) return NULL; f = malloc(sizeof *f); if (f == NULL) return NULL; *f = (struct upkg_file) { .pkg = pkg, .base = pkg->priv->exports[idx].offset, .len = pkg->priv->exports[idx].size, .name = pkg->priv->exports[idx].name, }; return f; } void upkg_export_close(struct upkg_file *f) { if (f->pkg->priv->last_file == f) f->pkg->priv->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) { int rc = EOF; switch (whence) { case SEEK_CUR: offset = f->offset + offset; case SEEK_SET: if (offset < 0 || offset > f->len) return EOF; rc = fseek(f->pkg->priv->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 = fseek(f->pkg->priv->f, f->base + offset, SEEK_SET); break; } if (rc == 0) f->pkg->priv->last_file = f; return rc; } size_t upkg_export_read(struct upkg_file *f, void *buf, size_t n) { size_t want = MIN(n, f->len - f->offset); size_t rc; if (want == 0) { return 0; } if (f != f->pkg->priv->last_file) { if (fseek(f->pkg->priv->f, f->base + f->offset, SEEK_SET)) return 0; } rc = fread(buf, 1, want, f->pkg->priv->f); f->offset += rc; return rc; }