/* * 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 #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; } /* * TODO: Make this use the uobject_module stuff so packages are not loaded * more than once. */ static GTypeModule * open_import_package(UObject *uo, const struct upkg_import *i) { GTypeModule *pkg; assert(i->parent == NULL); if (strcmp(i->class_package, "Core") != 0 || strcmp(i->class_name, "Package") != 0) { u_err(uo, "import root must be Core.Package"); return NULL; } pkg = u_pkg_open(i->name); if (!pkg || !g_type_module_use(pkg)) { u_err(uo, "failed to open package: %s", i->name); return NULL; } if (!U_PKG(pkg)->pkg) { g_type_module_unuse(pkg); u_err(uo, "failed to open package: %s", pkg->name); return NULL; } return pkg; } /* Find the export index of a package based on the import chain from another * package. Returns the (non-negative) offset on success, or a negative value * on failure. */ static long resolve_import(struct upkg *pkg, const struct upkg_import *import) { long current, index; if (!import->parent) return -1; current = resolve_import(pkg, import->parent); if (current != -1 && current >= 0) return -42; index = upkg_export_find(pkg, current, import->name); if (index < 0) return -42; return index; } static GObject *get_import_object(UObject *uo, unsigned long index) { const struct upkg_import *import; GObject *obj = NULL; GTypeModule *pkg; long obj_index; import = upkg_get_import(uo->pkg_file->pkg, index); if (!import) { u_err(uo, "invalid package import: %ld", index); return NULL; } /* Get the package name at the top of the hierarchy. */ for (const struct upkg_import *i = import; i; i = i->parent) { if (i->parent == NULL) { pkg = open_import_package(uo, i); if (!pkg) return NULL; } } obj_index = resolve_import(U_PKG(pkg)->pkg, import); if (obj_index < 0) { u_err(uo, "could not find import in package: %s", pkg->name); goto out; } obj = u_object_new_from_package(pkg, obj_index); out: g_type_module_unuse(pkg); return obj; } 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) { obj = get_import_object(uo, -(index+1)); } 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; } /* * Deserialize an IEEE 754 binary32 value in "little endian" (for whatever * that term is worth in this context). That is, when interpreted as a little * endian 32-bit unsigned integer: bit 31 is the sign, bits 30-23 are the * (biased) exponent, and bits 22-0 are the encoded part of the significand. * * The implementation is designed to be agnostic of the platform's actual * float type, but the conversion may be lossy if "float" is not itself a * binary32 format. NaN payloads are not preserved. */ static float unpack_binary32_le(const unsigned char *buf) { unsigned long raw; long significand; int exponent; float result; raw = unpack_32_le(buf); exponent = (raw & 0x7f800000) >> 23; significand = (raw & 0x007fffff) >> 0; switch (exponent) { case 255: result = significand ? NAN : INFINITY; break; default: significand |= 0x00800000; /* fall through */ case 0: result = ldexpf(significand, exponent-126-24); } return copysignf(result, raw & 0x80000000 ? -1 : 1); } 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 (head->size != 1 || priv->nbuf-len < head->size) 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 (head->size != 4 || priv->nbuf-len < head->size) 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: if (head->size != 0) return 0; 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; case PROPERTY_FLOAT: if (head->size != 4 || priv->nbuf-len < head->size) return 0; g_value_init(&val, G_TYPE_FLOAT); g_value_set_float(&val, unpack_binary32_le(priv->buf+len)); g_object_set_property(G_OBJECT(uo), head->prop_name, &val); break; default: u_warn(uo, "%s: unsupported property type %u", head->prop_name, (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 char *class, *package; GObject *obj = NULL; GType type; class = upkg_export_class(U_PKG(pkg)->pkg, idx, &package); if (!class) return NULL; 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); }