X-Git-Url: https://git.draconx.ca/gitweb/upkg.git/blobdiff_plain/c49355663c73c1775c179ec6360d09ce0978bb97..e3726c0480d74840dd9104850cba0f3e5f93beed:/src/uobject/uobject.c diff --git a/src/uobject/uobject.c b/src/uobject/uobject.c index 4bc1655..1a3d740 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,44 @@ #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,87 +69,203 @@ 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 (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, 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(o), name, &val); + 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(o), name, &val); + 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)type); + fprintf(stderr, "Unhandled property type %x\n", + (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. */ @@ -148,11 +276,10 @@ static int deserialize(UObject *uo) 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; @@ -162,26 +289,14 @@ static int deserialize(UObject *uo) 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(uo, name, f, len); - if (rc == 0) - return -1; - len = rc; - - tot_len += len; + if (head.type == PROPERTY_END) + break; } f->base += tot_len; @@ -216,6 +331,27 @@ int u_object_deserialize(GObject *obj, struct upkg *pkg, unsigned long idx) return rc; } +GObject *u_object_new_from_package(struct upkg *upkg, unsigned long idx) +{ + const struct upkg_export *export; + const char *class, *package; + GObject *obj = NULL; + GType type; + + class = upkg_export_class(upkg, idx, &package); + + type = u_object_module_get_class(package, class); + if (type) { + obj = g_object_new(type, NULL); + if (u_object_deserialize(obj, upkg, idx) != 0) { + g_object_unref(obj); + return NULL; + } + } + + return obj; +} + static void u_object_init(UObject *o) { o->pkg = NULL; @@ -241,3 +377,74 @@ 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_file) { + /* XXX: Currently, there's no way to get the full object name + * here because the object path information is discarded + * after opening it. In principle, there's no reason why + * we couldn't keep it around to improve log messages. */ + new_fmt = prepend_fmt(uo->pkg_file->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); +}