/* * upkg: tool for manipulating Unreal Tournament packages. * Copyright © 2009-2011 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 #include #include #include #include #include "upkg.h" #include "pack.h" #define U_OBJECT_GET_PRIV(o) \ G_TYPE_INSTANCE_GET_PRIVATE(o, U_TYPE_OBJECT, struct u_object_priv) struct prop_head { const char *prop_name, *struct_name; unsigned long size, array_idx; bool tag_msb; enum { PROPERTY_END, PROPERTY_BYTE, PROPERTY_INTEGER, PROPERTY_BOOLEAN, PROPERTY_FLOAT, PROPERTY_OBJECT, PROPERTY_NAME, PROPERTY_STRING, PROPERTY_CLASS, PROPERTY_ARRAY, PROPERTY_STRUCT, PROPERTY_VECTOR, PROPERTY_ROTATOR, PROPERTY_STR, PROPERTY_MAP, PROPERTY_FIXEDARRAY, } type; }; struct u_object_priv { struct upkg_file *f; size_t base, len; unsigned char buf[2048]; unsigned long nbuf; }; G_DEFINE_TYPE(UObject, u_object, G_TYPE_OBJECT); /* * Determine the actual size (in bytes) of a property, given the 3-bit encoded * size from the tag byte. Depending on the tag size, there will be 0, 1, 2 * or 4 additional size bytes, which are to be found in the provided buffer * which is at least n bytes long. The result is stored in *size. * * Returns the number of bytes that were read from the buffer, which may be 0. * On failure, (i.e., there was not enough material in the buffer) 0 is stored * in *size. */ static unsigned long decode_tag_size(unsigned long *size, unsigned tag_size, const unsigned char *buf, unsigned long n) { *size = 0; switch (tag_size) { case 0: *size = 1; return 0; case 1: *size = 2; return 0; case 2: *size = 4; return 0; case 3: *size = 12; return 0; case 4: *size = 16; return 0; case 5: if (n < 1) return 0; *size = buf[0]; return 1; case 6: if (n < 2) return 0; *size = unpack_16_le(buf); return 2; case 7: if (n < 4) return 0; *size = unpack_32_le(buf); return 4; } assert(0); } static unsigned long decode_array_index(unsigned long *index, const unsigned char *buf, unsigned long n) { if (n < 1) return 0; /* TODO: Actually implement this instead of aborting. */ assert("FIXME" && !(buf[0] & 0x80)); *index = buf[0]; return 1; } /* * Decode the (mostly) generic property header, filling out the struct pointed * to by head. Returns the number of bytes read from the buffer, or 0 on * failure. */ static unsigned long decode_prop_header(struct upkg *upkg, struct prop_head *head, const unsigned char *buf, unsigned long n) { unsigned long rc, len = 0; unsigned char tag_size; long index; rc = upkg_decode_index(&index, buf+len, n-len); if (rc == 0) return 0; if (!(head->prop_name = upkg_get_name(upkg, index))) return 0; len += rc; /* A property called "None" terminates the list, and does not have * the usual header. */ if (!strcmp(head->prop_name, "None")) { head->type = PROPERTY_END; head->size = 0; return len; } if (n-len < 1) return 0; head->tag_msb = (buf[len] >> 7) & 0x01; tag_size = (buf[len] >> 4) & 0x07; head->type = (buf[len] >> 0) & 0x0f; len++; /* * TODO: Confirm the correct relative ordering of the next three * fields. */ if (head->type == PROPERTY_STRUCT) { rc = upkg_decode_index(&index, buf+len, n-len); if (rc == 0) return 0; if (!(head->struct_name = upkg_get_name(upkg, index))) return 0; len += rc; } rc = decode_tag_size(&head->size, tag_size, buf+len, n-len); if (rc == 0 && head->size == 0) return 0; len += rc; head->array_idx = 0; if (head->tag_msb && head->type != PROPERTY_BOOLEAN) { rc = decode_array_index(&head->array_idx, buf+len, n-len); if (rc == 0) return 0; len += rc; } return len; } static int decode_object_property(UObject *uo, GValue *val, unsigned long len) { struct u_object_priv *priv = U_OBJECT_GET_PRIV(uo); GObject *obj = NULL; long index; int rc; rc = upkg_decode_index(&index, priv->buf+len, priv->nbuf-len); if (rc == 0 || index == 0) return -1; if (index < 0) { fprintf(stderr, "Imports not supported yet.\n"); } else { obj = u_object_new_from_package(uo->pkg, index-1); } g_value_init(val, U_TYPE_OBJECT); g_value_take_object(val, obj); return 0; } static unsigned long deserialize_property(UObject *uo, struct prop_head *head) { struct u_object_priv *priv = U_OBJECT_GET_PRIV(uo); unsigned long rc, len = 0; GValue val = {0}; rc = decode_prop_header(uo->pkg_file->pkg, head, priv->buf, priv->nbuf); if (rc == 0) return 0; len += rc; switch (head->type) { case PROPERTY_END: break; case PROPERTY_BYTE: if (priv->nbuf-len < 1) return 0; g_value_init(&val, G_TYPE_UCHAR); g_value_set_uchar(&val, priv->buf[len]); g_object_set_property(G_OBJECT(uo), head->prop_name, &val); break; case PROPERTY_INTEGER: if (priv->nbuf-len < 4) return 0; g_value_init(&val, G_TYPE_ULONG); g_value_set_ulong(&val, unpack_32_le(priv->buf+len)); g_object_set_property(G_OBJECT(uo), head->prop_name, &val); break; case PROPERTY_BOOLEAN: g_value_init(&val, G_TYPE_BOOLEAN); g_value_set_boolean(&val, head->tag_msb); g_object_set_property(G_OBJECT(uo), head->prop_name, &val); break; case PROPERTY_OBJECT: rc = decode_object_property(uo, &val, len); if (rc != 0) return 0; g_object_set_property(G_OBJECT(uo), head->prop_name, &val); break; default: fprintf(stderr, "Unhandled property type %x\n", (unsigned)head->type); } len += head->size; if (len > priv->nbuf) { long skip = len - priv->nbuf; /* XXX: Long properties are not supported yet, so just seek * past them for now. */ if (upkg_export_seek(uo->pkg_file, skip, SEEK_CUR) != 0) return 0; priv->nbuf = 0; } else { priv->nbuf -= len; memmove(priv->buf, priv->buf+len, priv->nbuf-len); } return len; } /* Deserialize properties from an Unreal package. */ static int deserialize(UObject *uo) { struct u_object_priv *priv = U_OBJECT_GET_PRIV(uo); struct upkg_file *f = uo->pkg_file; unsigned long rc, tot_len = 0; while (1) { struct prop_head head; /* Prime the buffer; deserialize_property assumes that there's * enough data for "small" properties already available. */ if (!f->eof) { void *buf = priv->buf + priv->nbuf; size_t amt = sizeof priv->buf - priv->nbuf; rc = upkg_export_read(f, buf, amt); if (rc == 0 && priv->nbuf == 0) return -1; priv->nbuf += rc; } rc = deserialize_property(uo, &head); if (rc == 0) { return -1; } tot_len += rc; if (head.type == PROPERTY_END) break; } f->base += tot_len; f->len -= tot_len; upkg_export_seek(f, 0, SEEK_SET); return 0; } /* * Get the full hierarchical object name for an export, used for diagnostics. * The package name is passed in pname. * * Returns a buffer allocated by malloc on success, or NULL on failure. */ static char *get_obj_fullname(const char *pname, const struct upkg_export *e) { size_t total_len = strlen(pname) + 1, len; char *fullname; assert(e != NULL); for (const struct upkg_export *c = e; c; c = c->parent) { len = strlen(c->name) + 1; if (total_len > SIZE_MAX - len) return NULL; total_len += len; } fullname = malloc(total_len); if (!fullname) return NULL; for (const struct upkg_export *c = e; c; c = c->parent) { len = strlen(c->name); assert(total_len > len); total_len -= len + 1; memcpy(fullname + total_len, c->name, len); fullname[total_len + len] = c == e ? '\0' : '.'; } assert(total_len == strlen(pname)+1); memcpy(fullname, pname, total_len-1); fullname[total_len-1] = '.'; return fullname; } int u_object_deserialize(GObject *obj, GTypeModule *pkg, unsigned long idx) { g_return_val_if_fail(IS_U_OBJECT(obj), -1); g_return_val_if_fail(IS_U_PKG(pkg), -1); UObject *uo = U_OBJECT(obj); struct upkg *upkg = U_PKG(pkg)->pkg; const struct upkg_export *e; struct upkg_file *f; int rc; g_return_val_if_fail(uo->pkg_file == NULL, -1); f = upkg_export_open(upkg, idx); if (!f) { return -1; } uo->pkg = pkg; uo->pkg_file = f; e = upkg_get_export(upkg, idx); if (!e) return -1; uo->pkg_name = get_obj_fullname(pkg->name, e); if (!uo->pkg_name) return -1; rc = U_OBJECT_GET_CLASS(obj)->deserialize(uo); if (rc != 0) { upkg_export_close(f); uo->pkg_file = NULL; } return rc; } GObject *u_object_new_from_package(GTypeModule *pkg, unsigned long idx) { g_return_val_if_fail(IS_U_PKG(pkg), NULL); const struct upkg_export *export; const char *class, *package; GObject *obj = NULL; GType type; class = upkg_export_class(U_PKG(pkg)->pkg, idx, &package); type = u_object_module_get_class(package, class); if (type) { obj = g_object_new(type, NULL); if (u_object_deserialize(obj, pkg, idx) != 0) { g_object_unref(obj); return NULL; } } return obj; } static void u_object_init(UObject *o) { o->pkg = NULL; o->pkg_file = NULL; } static void u_object_finalize(GObject *o) { UObject *uo = U_OBJECT(o); if (uo->pkg_file) { upkg_export_close(uo->pkg_file); } free(uo->pkg_name); G_OBJECT_CLASS(u_object_parent_class)->finalize(o); } static void u_object_class_init(UObjectClass *class) { g_type_class_add_private(class, sizeof (struct u_object_priv)); GObjectClass *go = G_OBJECT_CLASS(class); class->deserialize = deserialize; go->finalize = u_object_finalize; } /* * Prepend a prefix to a printf-style format string. Unfortunately, there's * no way to construct va_list values on the fly in C, so we cannot simply * prepend %s and pass the prefix as an argument. Any % characters in the * prefix will be escaped. * * Returns a pointer to a newly allocated string, which should be freed by the * caller, or NULL otherwise. */ static char *prepend_fmt(const char *prefix, const char *fmt) { size_t prefix_len = strlen(prefix), fmt_len = strlen(fmt); char *new_fmt; if (prefix_len > SIZE_MAX/2 - sizeof ": ") return NULL; if (2*prefix_len > SIZE_MAX - fmt_len) return NULL; new_fmt = malloc(2*prefix_len + fmt_len + 1); if (new_fmt) { size_t offset = 0; for (size_t i = 0; i < prefix_len; i++) { if (prefix[i] == '%') new_fmt[offset++] = '%'; new_fmt[offset++] = prefix[i]; } new_fmt[offset++] = ':'; new_fmt[offset++] = ' '; strcpy(new_fmt+offset, fmt); } return new_fmt; } /* Logging helpers that automatically prepend the UObject class information. */ void u_vlog_full(GObject *o, GLogLevelFlags level, const char *fmt, va_list ap) { g_return_if_fail(IS_U_OBJECT(o)); UObject *uo = U_OBJECT(o); char *new_fmt = NULL; if (uo->pkg_name) { new_fmt = prepend_fmt(uo->pkg_name, fmt); if (!new_fmt) { g_log(G_OBJECT_TYPE_NAME(o), level, "%s", uo->pkg_file->name); } else { fmt = new_fmt; } } g_logv(G_OBJECT_TYPE_NAME(o), level, fmt, ap); free(new_fmt); } void u_log_full(GObject *o, GLogLevelFlags level, const char *fmt, ...) { va_list ap; va_start(ap, fmt); u_vlog_full(o, level, fmt, ap); va_end(ap); }