X-Git-Url: https://git.draconx.ca/gitweb/upkg.git/blobdiff_plain/141ec2de0dca3b1a2482787360fc111c6bcbff3f..d42bc5c2d8dcf388b424c5bbc4b6f25416f689f1:/src/uobject/uobject.c diff --git a/src/uobject/uobject.c b/src/uobject/uobject.c index e5b500b..331b8ae 100644 --- a/src/uobject/uobject.c +++ b/src/uobject/uobject.c @@ -1,6 +1,6 @@ /* * 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 * it under the terms of the GNU General Public License as published by @@ -19,32 +19,46 @@ #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_OBJECT_TYPE, struct u_object_priv) - -enum { - PROPERTY_BYTE = 1, - 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, + 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 { @@ -57,101 +71,353 @@ struct u_object_priv { 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 -get_real_size(unsigned long *real, unsigned size, unsigned char *buf, size_t n) +decode_tag_size(unsigned long *size, unsigned tag_size, + const unsigned char *buf, unsigned long n) { - assert(size < 8); - - *real = 0; - switch (size) { - case 0: *real = 1; return 0; - case 1: *real = 2; return 0; - case 2: *real = 4; return 0; - case 3: *real = 12; return 0; - case 4: *real = 16; return 0; + *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; - *real = *buf; + if (n < 1) + return 0; + *size = buf[0]; return 1; case 6: - if (n < 2) return 0; - *real = unpack_16_le(buf); + if (n < 2) + return 0; + *size = unpack_16_le(buf); return 2; case 7: - if (n < 4) return 0; - *real = unpack_32_le(buf); + if (n < 4) + return 0; + *size = unpack_32_le(buf); return 4; } - return 0; + assert(0); } static unsigned long -decode_property(UObject *o, const char *name, struct upkg_file *f, unsigned long len) +decode_array_index(unsigned long *index, const unsigned char *buf, + unsigned long n) { - struct u_object_priv *priv = U_OBJECT_GET_PRIV(o); - unsigned long real_size, rc; - int type, size, top; - GValue val = {0}; + if (n < 1) + return 0; + + /* TODO: Actually implement this instead of aborting. */ + assert("FIXME" && !(buf[0] & 0x80)); - if (priv->nbuf-len < 1) + *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; - - type = (priv->buf[len] >> 0) & 0x0f; - size = (priv->buf[len] >> 4) & 0x07; - top = (priv->buf[len] >> 7) & 0x01; - len += 1; - - rc = get_real_size(&real_size, size, priv->buf, priv->nbuf-len); - if (real_size == 0) + if (!(head->prop_name = upkg_get_name(upkg, index))) return 0; len += rc; - switch (type) { + /* 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 (priv->nbuf-len < 1) + 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(o), name, &val); + g_object_set_property(G_OBJECT(uo), head->prop_name, &val); break; case PROPERTY_INTEGER: - if (priv->nbuf-len < 4) + 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(o), name, &val); + 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: - fprintf(stderr, "Unhandled property type %x\n", (unsigned)type); + u_warn(uo, "%s: unsupported property type %u", + head->prop_name, (unsigned)head->type); } + len += head->size; - real_size += len; - if (real_size + len <= priv->nbuf) { - priv->nbuf -= real_size; - memmove(priv->buf, priv->buf+real_size, priv->nbuf); - } else { - long skip = real_size - priv->nbuf; - if (upkg_export_seek(f, skip, SEEK_CUR) != 0) + 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 real_size; + return len; } /* Deserialize properties from an Unreal package. */ -static int deserialize(UObject *o, struct upkg_file *f) +static int deserialize(UObject *uo) { - struct u_object_priv *priv = U_OBJECT_GET_PRIV(o); + 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) { - unsigned long len = 0; - const char *name; - long tmp; + struct prop_head head; - /* Read some data into buffer. */ + /* 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; @@ -161,26 +427,14 @@ static int deserialize(UObject *o, struct upkg_file *f) priv->nbuf += rc; } - /* Get the property name. */ - rc = upkg_decode_index(&tmp, priv->buf+len, priv->nbuf-len); - if (rc == 0) - return -1; - len = rc; - - name = upkg_get_name(f->pkg, tmp); - if (!name) { + rc = deserialize_property(uo, &head); + if (rc == 0) { return -1; - } else if (strcmp(name, "None") == 0) { - tot_len += len; - break; } + tot_len += rc; - rc = decode_property(U_OBJECT(o), name, f, len); - if (rc == 0) - return -1; - len = rc; - - tot_len += len; + if (head.type == PROPERTY_END) + break; } f->base += tot_len; @@ -190,30 +444,106 @@ static int deserialize(UObject *o, struct upkg_file *f) return 0; } -int u_object_deserialize(GObject *obj, struct upkg *pkg, unsigned long idx) +/* + * 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(pkg, idx); + f = upkg_export_open(upkg, idx); if (!f) { return -1; } - rc = U_OBJECT_GET_CLASS(obj)->deserialize(uo, f); + 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); - } else { - uo->pkg = pkg; - uo->pkg_file = 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; @@ -228,6 +558,8 @@ static void u_object_finalize(GObject *o) upkg_export_close(uo->pkg_file); } + free(uo->pkg_name); + G_OBJECT_CLASS(u_object_parent_class)->finalize(o); } @@ -239,3 +571,70 @@ static void u_object_class_init(UObjectClass *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); +}