]> git.draconx.ca Git - upkg.git/commitdiff
texture: Add initial support for texture exports.
authorNick Bowler <nbowler@draconx.ca>
Tue, 8 May 2012 23:53:20 +0000 (19:53 -0400)
committerNick Bowler <nbowler@draconx.ca>
Tue, 8 May 2012 23:53:20 +0000 (19:53 -0400)
After almost 3 years, we can finally export at least some textures!

src/engine/Makefile.inc
src/engine/pcx.c [new file with mode: 0644]
src/engine/pcx.h [new file with mode: 0644]
src/engine/texture.gob

index e445cde809afd21958cbe0935c1c0dbe34b2ab47..dd198b933f51eee14179b8f765261f8f197dc6b5 100644 (file)
@@ -14,7 +14,7 @@ MAINTAINERCLEANFILES += $(engine_GOBS:.gob=.gobstamp) \
        $(engine_GOBS:.gob=.c) $(engine_GOBS:.gob=.h)
 
 pkglib_LTLIBRARIES += engine.la
-engine_la_SOURCES   = engine/engine.c $(engine_GOBS:.gob=.c)
+engine_la_SOURCES   = engine/engine.c engine/pcx.c $(engine_GOBS:.gob=.c)
 engine_la_CFLAGS    = $(GLIB_CFLAGS)
 engine_la_LIBADD    = $(GLIB_LIBS)
 engine_la_LDFLAGS   = -module -avoid-version -export-symbols-regex _LTX_
diff --git a/src/engine/pcx.c b/src/engine/pcx.c
new file mode 100644 (file)
index 0000000..b01e9b8
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ * upkg: a tool for manipulating Unreal Tournament packages.
+ * Very simple PCX writer for saving extracted textures.
+ *
+ * Copyright © 2012 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+
+#include <engine/palette.h>
+#include <pack.h>
+
+#include "pcx.h"
+
+int pcx_init_header(struct pcx_head *head)
+{
+       unsigned short stride;
+
+       if (head->width == 0 || head->width > 0xffff)
+               return -1;
+       if (head->height == 0 || head->height > 0xffff)
+               return -1;
+
+       stride = head->width + (head->width & 1);
+       if (stride == 0 || stride > 0xffff)
+               return -1;
+
+       memset(head->encoded, 0, sizeof head->encoded);
+
+       head->encoded[0] = 10; /* PCX Magic */
+       head->encoded[1] = 5;  /* Version 3. */
+       head->encoded[2] = 1;  /* Run-length encoding. */
+       head->encoded[3] = 8;  /* Bits per pixel. */
+
+       pack_16_le(head->encoded+8,  head->width-1);
+       pack_16_le(head->encoded+10, head->height-1);
+
+       head->encoded[65] = 1; /* One plane (indexed colour). */
+       pack_16_le(head->encoded+66, stride);
+
+       head->encoded[68] = 1; /* Colour palette. */
+
+       return 0;
+}
+
+int pcx_write_scanline(const struct pcx_head *head, const unsigned char *src,
+                                                    FILE *f)
+{
+       size_t len = head->width, offset = 0;
+       unsigned char buf[head->width*2ul];
+
+       for (size_t i = 0; i < len;) {
+               unsigned run = 0;
+
+               while (i+run < len && src[i] == src[i+run] && run <= 0x3f)
+                       run++;
+
+               if (run > 1 || src[i] > 0xbf)
+                       buf[offset++] = 0xc0 | run;
+               buf[offset++] = src[i];
+               i += run;
+       }
+
+       if (fwrite(buf, offset, 1, f) != 1)
+               return -1;
+       return 0;
+}
+
+int pcx_write_palette(EnginePalette *palette, FILE *f)
+{
+       unsigned char buf[769] = {12};
+
+       for (unsigned i = 0; i < palette->entries; i++) {
+               buf[3*i+1] = palette->rgba[i][0];
+               buf[3*i+2] = palette->rgba[i][1];
+               buf[3*i+3] = palette->rgba[i][2];
+       }
+
+       if (fwrite(buf, sizeof buf, 1, f) != 1)
+               return -1;
+       return 0;
+}
diff --git a/src/engine/pcx.h b/src/engine/pcx.h
new file mode 100644 (file)
index 0000000..48941d8
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * upkg: a tool for manipulating Unreal Tournament packages.
+ * Very simple PCX writer for saving extracted textures.
+ *
+ * Copyright © 2012 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef ENGINE_PCX_H_
+#define ENGINE_PCX_H_
+
+#include <engine/palette.h>
+
+struct pcx_head {
+       unsigned short width, height;
+       unsigned char encoded[128];
+};
+
+int pcx_init_header(struct pcx_head *head);
+int pcx_write_scanline(const struct pcx_head *head, const unsigned char *src, FILE *f);
+int pcx_write_palette(EnginePalette *palette, FILE *f);
+
+#endif
index 35dd739a439210762256419b70b319610ae4f989..3db176e045982b31de94268bed9031a7f63cf889 100644 (file)
 
 %{
 #include <stdio.h>
+#include <stdlib.h>
 #include <string.h>
+#include <uobject/loadable.h>
+#include <uobject/exportable.h>
 #include <engine/palette.h>
+#include "pack.h"
+#include "pcx.h"
 %}
 
 %h{
 #include <uobject/uobject.h>
+
+struct engine_texture_data {
+       unsigned long width, height;
+
+       unsigned long datalen;
+       unsigned char data[];
+};
+
+%}
+
+%{
+struct engine_texture_data *decode_mipmap(UObject *uo)
+{
+       struct upkg_file *f = uo->pkg_file;
+       struct engine_texture_data *data;
+       size_t rc, pos = 0, buflen;
+       unsigned long end_offset;
+       unsigned char buf[32];
+       long datalen;
+
+       buflen = upkg_export_read(f, buf, sizeof buf);
+       if (uo->pkg->version >= 63) {
+               /*
+                * There's an offset to the end of the image data here; not
+                * clear why it's useful since it's implied by the very next
+                * field anyway.  However, we will nevertheless validate this
+                * property below.
+                */
+               if (pos + 4 > buflen)
+                       return NULL;
+               end_offset = unpack_32_le(buf);
+               pos += 4;
+       }
+
+       rc = upkg_decode_index(&datalen, buf+pos, buflen-pos);
+       if (rc == 0 || datalen < 0)
+               return NULL;
+       pos += rc;
+
+       data = malloc(sizeof *data + datalen);
+       if (!data)
+               return NULL;
+       data->datalen = datalen;
+
+       /* Rewind to the start of the image data, and slurp it in. */
+       if (upkg_export_seek(f, -(long)(buflen-pos), SEEK_CUR) != 0)
+               goto err_free;
+       rc = upkg_export_read(f, data->data, data->datalen);
+       if (rc != data->datalen)
+               goto err_free;
+
+       /* At this point, the current file offset should match the one recorded
+        * above. */
+       if (uo->pkg->version >= 63 && end_offset != (f->base + f->offset))
+               goto err_free;
+
+       /* Read in the remaining fields */
+       buflen = upkg_export_read(f, buf, 10);
+       if (buflen < 10)
+               goto err_free;
+
+       data->width  = unpack_32_le(buf+0);
+       data->height = unpack_32_le(buf+4);
+       /* Last 2 bytes seem to be simply the base-2 log of width/height for
+        * whatever reason.  Ignore them for now. */
+
+       return data;
+err_free:
+       free(data);
+       return NULL;
+}
 %}
 
 class Engine:Texture from U:Object (dynamic)
+       (interface U:Object:Exportable)
+       (interface U:Object:Loadable)
 {
        private unsigned USize;
        private unsigned UClamp;
@@ -39,6 +117,105 @@ class Engine:Texture from U:Object (dynamic)
 
        private Engine:Palette *Palette;
 
+       private struct engine_texture_data **mipmap_data;
+       private unsigned char mipmap_count;
+
+       interface U:Object:Loadable
+       private int load(U:Object *uo)
+       {
+               struct engine_texture_data **data;
+               Self *self = SELF(uo);
+
+               if (upkg_export_seek(uo->pkg_file, 0, SEEK_SET) != 0)
+                       return -1;
+
+               data = malloc(self->_priv->mipmap_count * sizeof *data);
+               if (!data)
+                       return -1;
+
+               for (int i = 0; i < self->_priv->mipmap_count; i++) {
+                       data[i] = decode_mipmap(uo);
+                       if (!data[i]) {
+                               /* Unwind the allocations. */
+                               for (; i >= 0; i--)
+                                       free(data[i]);
+                               free(data);
+                               return -1;
+                       }
+               }
+
+               self->_priv->mipmap_data = data;
+               return 0;
+       }
+
+       interface U:Object:Loadable
+       private void unload(U:Object *uo)
+       {
+               Self *self = SELF(uo);
+
+               for (int i = 0; i < self->_priv->mipmap_count; i++)
+                       free(self->_priv->mipmap_data[i]);
+               free(self->_priv->mipmap_data);
+               self->_priv->mipmap_data = NULL;
+       }
+
+       interface U:Object:Exportable
+       private int export_name(U:Object *uo, char *buf, size_t n)
+       {
+               return snprintf(buf, n, "%s.pcx", uo->pkg_file->name);
+       }
+
+       interface U:Object:Exportable
+       private int export(U:Object *uo, FILE *f)
+       {
+               Self *self = SELF(uo);
+               struct engine_texture_data *data;
+               struct pcx_head head;
+
+               if (!self->_priv->mipmap_data || !self->_priv->Palette)
+                       return -1;
+               data = self->_priv->mipmap_data[0];
+
+               head.width  = data->width;
+               head.height = data->height;
+
+               if (pcx_init_header(&head) != 0)
+                       return -1;
+               if (fwrite(head.encoded, sizeof head.encoded, 1, f) != 1)
+                       return -1;
+
+               for (unsigned i = 0; i < head.height; i++) {
+                       if (pcx_write_scanline(&head, data->data+i*data->width,
+                                              f) != 0) {
+                               return -1;
+                       }
+               }
+
+               if (pcx_write_palette(self->_priv->Palette, f) != 0)
+                       return -1;
+
+               return 0;
+       }
+
+       override (U:Object) int deserialize(U:Object *uo)
+       {
+               struct upkg_file *f = uo->pkg_file;
+               Self *self = SELF(uo);
+
+               PARENT_HANDLER(uo);
+
+               if (upkg_export_read(f, &self->_priv->mipmap_count, 1) != 1)
+                       return -1;
+               if (self->_priv->mipmap_count == 0)
+                       return -1;
+
+               f->base += 1;
+               f->len  -= 1;
+               upkg_export_seek(f, 0, SEEK_SET);
+
+               return 0;
+       }
+
        property UINT USize
                ( nick = "USize"
                , blurb = "Width of the texture."