--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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
%{
#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;
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."