From a0efa108741b6cb53ddc3b1228a91b2e0c75a56a Mon Sep 17 00:00:00 2001 From: Nick Bowler Date: Tue, 8 May 2012 19:53:20 -0400 Subject: [PATCH] texture: Add initial support for texture exports. After almost 3 years, we can finally export at least some textures! --- src/engine/Makefile.inc | 2 +- src/engine/pcx.c | 98 ++++++++++++++++++++++ src/engine/pcx.h | 35 ++++++++ src/engine/texture.gob | 177 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 311 insertions(+), 1 deletion(-) create mode 100644 src/engine/pcx.c create mode 100644 src/engine/pcx.h diff --git a/src/engine/Makefile.inc b/src/engine/Makefile.inc index e445cde..dd198b9 100644 --- a/src/engine/Makefile.inc +++ b/src/engine/Makefile.inc @@ -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 index 0000000..b01e9b8 --- /dev/null +++ b/src/engine/pcx.c @@ -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 . + */ + +#include +#include +#include +#include + +#include +#include + +#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 index 0000000..48941d8 --- /dev/null +++ b/src/engine/pcx.h @@ -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 . + */ + +#ifndef ENGINE_PCX_H_ +#define ENGINE_PCX_H_ + +#include + +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 diff --git a/src/engine/texture.gob b/src/engine/texture.gob index 35dd739..3db176e 100644 --- a/src/engine/texture.gob +++ b/src/engine/texture.gob @@ -20,15 +20,93 @@ %{ #include +#include #include +#include +#include #include +#include "pack.h" +#include "pcx.h" %} %h{ #include + +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." -- 2.43.0