+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <glib-object.h>
+
+#include "uobject.h"
+#include "upkg.h"
+#include "pack.h"
+#include "avl.h"
+
+#define U_OBJECT_GET_PRIV(o) \
+ G_TYPE_INSTANCE_GET_PRIVATE(o, U_OBJECT_TYPE, struct uobject_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,
+};
+
+struct uobject_property {
+ const char *name;
+ GValue val;
+};
+
+struct uobject_priv {
+ struct upkg_file *f;
+ size_t base, len;
+
+ struct avl_table *properties;
+
+ unsigned char buf[2048];
+ unsigned long nbuf;
+};
+
+/* AVL tree functions. */
+static int propcmp(const void *_a, const void *_b, void *_data)
+{
+ const struct uobject_property *a = _a, *b = _b;
+ return strcmp(a->name, b->name);
+}
+
+static void propfree(void *item, void *data)
+{
+ free(item);
+}
+
+static unsigned long
+get_real_size(unsigned long *real, unsigned size, unsigned char *buf, size_t 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;
+ case 5:
+ if (n < 1) return 0;
+ *real = *buf;
+ return 1;
+ case 6:
+ if (n < 2) return 0;
+ *real = unpack_16_le(buf);
+ return 2;
+ case 7:
+ if (n < 4) return 0;
+ *real = unpack_32_le(buf);
+ return 4;
+ }
+
+ return 0;
+}
+
+static unsigned long
+decode_property(UObject *o, const char *name, struct upkg_file *f, unsigned long len)
+{
+ struct uobject_priv *priv = U_OBJECT_GET_PRIV(o);
+ unsigned long real_size, rc;
+ int type, size, top;
+ GValue val = {0};
+
+ if (priv->nbuf-len < 1)
+ 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)
+ return 0;
+ len += rc;
+
+ switch (type) {
+ 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]);
+ u_object_set_property(o, 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));
+ u_object_set_property(o, name, &val);
+ break;
+ default:
+ fprintf(stderr, "Unhandled property type %x\n", (unsigned)type);
+ }
+
+ 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)
+ return 0;
+ priv->nbuf = 0;
+ }
+
+ return real_size;
+}
+
+/* Deserialize properties from an Unreal package. */
+static int deserialize(UObject *o, struct upkg_file *f)
+{
+ struct uobject_priv *priv = U_OBJECT_GET_PRIV(o);
+ unsigned long rc, tot_len = 0;
+
+ while (1) {
+ unsigned long len = 0;
+ const char *name;
+ long tmp;
+
+ /* Read some data into buffer. */
+ 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)
+ return -1;
+ 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) {
+ return -1;
+ } else if (strcmp(name, "None") == 0) {
+ tot_len += rc;
+ break;
+ }
+
+ rc = decode_property(U_OBJECT(o), name, f, len);
+ if (rc == 0)
+ return -1;
+ len += rc;
+
+ tot_len += len;
+ }
+
+ f->base += tot_len;
+ f->len -= tot_len;
+ upkg_export_seek(f, 0, SEEK_SET);
+
+ return 0;
+}
+
+int u_object_deserialize(GObject *obj, struct upkg_file *f)
+{
+ g_return_val_if_fail(IS_U_OBJECT(obj), -1);
+ U_OBJECT_GET_CLASS(obj)->deserialize(obj, f);
+}
+
+static void u_object_init(UObject *o)
+{
+ struct uobject_priv *priv = U_OBJECT_GET_PRIV(o);
+ priv->properties = NULL;
+}
+
+void u_object_set_property(UObject *o, const char *name, const GValue *val)
+{
+ struct uobject_priv *priv = U_OBJECT_GET_PRIV(o);
+ struct uobject_property *prop, search = { .name = name };
+ void **p;
+
+ if (!priv->properties) {
+ priv->properties = avl_create(propcmp, NULL, NULL);
+ g_return_if_fail(priv->properties != NULL);
+ }
+
+ prop = avl_find(priv->properties, &search);
+ if (prop) {
+ g_value_unset(&prop->val);
+ g_value_init(&prop->val, G_VALUE_TYPE(val));
+ g_value_copy(val, &prop->val);
+ return;
+ }
+
+ prop = malloc(sizeof *prop);
+ g_return_if_fail(prop != NULL);
+
+ *prop = (struct uobject_property) { .name = name };
+ g_value_init(&prop->val, G_VALUE_TYPE(val));
+ g_value_copy(val, &prop->val);
+
+ g_return_if_fail(avl_probe(priv->properties, &prop) != NULL);
+}
+
+const GValue *u_object_get_property(UObject *o, const char *name)
+{
+ struct uobject_priv *priv = U_OBJECT_GET_PRIV(o);
+ struct uobject_property *prop, search = { .name = name };
+
+ if (!priv->properties)
+ return NULL;
+
+ prop = avl_find(priv->properties, &search);
+ if (!prop)
+ return NULL;
+ return &prop->val;
+}
+
+static void u_object_finalize(GObject *o)
+{
+ struct uobject_priv *priv = U_OBJECT_GET_PRIV(o);
+
+ if (priv->properties) {
+ avl_destroy(priv->properties, propfree);
+ priv->properties = NULL;
+ }
+}
+
+static void u_object_class_init(UObjectClass *class)
+{
+ GObjectClass *go = G_OBJECT_CLASS(class);
+
+ class->deserialize = deserialize;
+
+ g_type_class_add_private(class, sizeof (struct uobject_priv));
+ go->finalize = u_object_finalize;
+}
+
+G_DEFINE_TYPE(UObject, u_object, G_TYPE_OBJECT);