%alltop{
/*
* upkg: tool for manipulating Unreal Tournament packages.
* Copyright © 2020, 2022 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 .
*/
%}
%ctop{
#include
%}
%{
#include
#include
#include
#include "pack.h"
#ifndef SIZE_MAX
# define SIZE_MAX ((size_t)-1)
#endif
%}
%h{
#include
#include
/* Hack to work around broken type parsing in GOB2. */
typedef float engine_mesh_vertex[3];
struct engine_mesh_face {
unsigned long flags;
unsigned short verts[3];
unsigned short texture;
float uv[3][2];
};
%}
class Engine:Mesh from U:Object (dynamic)
(interface U:Object:Loadable)
(interface U:Object:Exportable)
{
private struct upkg_file pkg_file;
protected long vert_base;
protected long vert_num;
protected long tri_base;
protected long tri_num;
protected long conn_base;
protected long conn_num;
protected long vlink_base;
protected long vlink_num;
protected engine_mesh_vertex *vertices;
protected struct engine_mesh_face *faces;
protected long num_textures = 0;
protected Engine:Texture **textures destroy {
long i;
for (i = 0; i < self->num_textures; i++) {
if (textures[i])
g_object_unref(textures[i]);
}
free(textures);
};
private int skip_array(struct UObject *uo, long *nent, long entsz,
unsigned char *buf, size_t buflen)
{
struct upkg_file *f = uo->pkg_file;
unsigned long buf_offset = f->offset - buflen;
unsigned char mybuf[9];
unsigned long skip;
size_t sz, pos = 0;
if (!buf) {
buf_offset = f->offset;
buflen = upkg_export_read(f, mybuf, sizeof mybuf);
buf = mybuf;
}
if (f->pkg->version >= 62) {
if (buflen < 4)
return -1;
skip = unpack_32_le(buf);
pos += 4;
}
pos += (sz = upkg_decode_index(nent, buf+pos, buflen-pos));
if (sz == 0)
return -1;
if (*nent > SIZE_MAX / entsz) {
u_warn(uo, "bogus array size");
return -1;
}
sz = (size_t)*nent * entsz;
if (f->pkg->version >= 62 && sz != skip - f->base - buf_offset - pos) {
u_warn(uo, "array skip does not match array size");
return -1;
}
if (upkg_export_seek(f, buf_offset + pos + sz, SEEK_SET) != 0)
return -1;
return 0;
}
private size_t fill_buf(struct upkg_file *f, unsigned char *buf,
size_t pos, size_t buflen)
{
size_t rc, rem = buflen - pos;
memmove(buf, buf+pos, rem);
rem += upkg_export_read(f, buf+rem, buflen-rem);
return rem;
}
private int load_vertices(Self *self)
{
struct upkg_file *f = &self->_priv->pkg_file;
unsigned char buf[100];
size_t buflen, pos;
long i, n;
if (upkg_export_seek(f, self->vert_base, SEEK_SET) != 0)
return -1;
buflen = upkg_export_read(f, buf, sizeof buf);
pos = upkg_decode_index(&n, buf, buflen);
g_return_val_if_fail(pos && n == self->vert_num, -1);
if (self->vert_num > SIZE_MAX / sizeof self->vertices[0]) {
u_err(self, "failed to allocate memory");
return -1;
}
self->vertices = malloc(self->vert_num * sizeof self->vertices[0]);
if (!self->vertices) {
u_err(self, "failed to allocate memory");
return -1;
}
for (i = 0; i < n; i++) {
unsigned long vertex;
if (buflen - pos < 4) {
buflen = self_fill_buf(f, buf, pos, buflen);
pos = 0;
}
if (buflen - pos < 4) {
u_err(self, "failed to read vertex data");
free(self->vertices);
return -1;
}
vertex = unpack_32_le(buf+pos); pos += 4;
self->vertices[i][0] = (vertex & 0x7ff) / 8.0;
if (self->vertices[i][0] >= 128)
self->vertices[i][0] -= 256;
self->vertices[i][1] = ((vertex >> 11) & 0x7ff) / 8.0;
if (self->vertices[i][1] >= 128)
self->vertices[i][1] -= 256;
self->vertices[i][2] = ((vertex >> 22) & 0x3ff) / 4.0;
if (self->vertices[i][2] >= 128)
self->vertices[i][2] -= 256;
}
return 0;
}
private int load_faces(Self *self)
{
struct upkg_file *f = &self->_priv->pkg_file;
unsigned char buf[100];
size_t buflen, pos;
long i, n;
if (upkg_export_seek(f, self->tri_base, SEEK_SET) != 0)
return -1;
buflen = upkg_export_read(f, buf, sizeof buf);
pos = upkg_decode_index(&n, buf, buflen);
g_return_val_if_fail(pos && n == self->tri_num, -1);
if (self->tri_num > SIZE_MAX / sizeof self->faces[0]) {
u_err(self, "failed to allocate memory");
return -1;
}
self->faces = malloc(self->tri_num * sizeof self->faces[0]);
if (!self->faces) {
u_err(self, "failed to allocate memory");
return -1;
}
for (i = 0; i < n; i++) {
struct engine_mesh_face *face = &self->faces[i];
unsigned long tmp;
int j;
if (buflen - pos < 20) {
buflen = self_fill_buf(f, buf, pos, buflen);
pos = 0;
}
if (buflen - pos < 20)
goto read_err;
for (j = 0; j < 3; j++) {
face->verts[j] = unpack_16_le(buf + pos + 2*j);
if (face->verts[j] >= self->vert_num)
goto read_err;
face->uv[j][0] = buf[pos + 6 + 2*j] / 255.0;
face->uv[j][1] = 1 - buf[pos + 7 + 2*j] / 255.0;
}
face->flags = unpack_32_le(buf+pos+12);
tmp = unpack_32_le(buf+pos+16);
if (tmp > USHRT_MAX || tmp >= self->num_textures)
goto read_err;
face->texture = tmp;
pos += 20;
}
return 0;
read_err:
u_err(self, "failed to read triangle data");
free(self->faces);
return -1;
}
interface U:Object:Loadable
private int load(U:Object *uo)
{
Self *self = SELF(uo);
if (self_load_vertices(self) != 0) {
u_err(self, "error loading vertex data");
return -1;
}
if (self_load_faces(self) != 0) {
u_err(self, "error loading face data");
free(self->vertices);
return -1;
}
return 0;
}
interface U:Object:Loadable
private void unload(U:Object *uo)
{
Self *self = SELF(uo);
free(self->vertices);
free(self->faces);
}
interface U:Object:Exportable
private int export_name(U:Object *uo, char *buf, size_t n)
{
return snprintf(buf, n, "%s.obj", uo->pkg_file->name);
}
private int uv_compar(const void *a_, const void *b_)
{
const float *a = a_, *b = b_;
if (a[0] < b[0])
return -1;
if (a[0] > b[0])
return 1;
if (a[1] < b[1])
return -1;
if (a[1] > b[1])
return 1;
return 0;
}
interface U:Object:Exportable
private int export(U:Object *uo, FILE *f)
{
Self *self = SELF(uo);
int rc, ret = -1;
long i;
float (*uv_work)[2] = NULL;
long uv_num = 0;
long last_texture = -1;
if (fprintf(f, "mtllib %s.mtl\n", uo->pkg_file->name) < 0)
goto out;
if (fprintf(f, "# %ld vertices\n", self->vert_num) < 0)
goto out;
for (i = 0; i < self->vert_num; i++) {
float *v = self->vertices[i];
if (fprintf(f, "v %f %f %f\n", v[0], v[1], v[2]) < 0)
goto out;
}
/* Optimize UV maps */
uv_work = malloc(3 * self->tri_num * sizeof uv_work[0]);
if (!uv_work) {
u_warn(uo, "failed to allocate memory");
uv_num = 3 * self->tri_num;
} else {
for (i = 0; i < self->tri_num; i++) {
int j;
for (j = 0; j < 3; j++) {
uv_work[3*i+j][0] = self->faces[i].uv[j][0];
uv_work[3*i+j][1] = self->faces[i].uv[j][1];
}
}
qsort(uv_work, 3 * self->tri_num, sizeof uv_work[0],
self_uv_compar);
for (i = 0; i < 3 * self->tri_num; i++) {
if (i > 0 && !self_uv_compar(uv_work[i-1], uv_work[i]))
continue;
memmove(uv_work[uv_num], uv_work[i], sizeof uv_work[0]);
uv_num++;
}
}
if (fprintf(f, "# %ld texture coords\n", uv_num) < 0)
goto out;
for (i = 0; i < uv_num; i++) {
float *uv;
if (uv_work)
uv = uv_work[i];
else
uv = self->faces[i/3].uv[i%3];
if (fprintf(f, "vt %f %f\n", uv[0], uv[1]) < 0)
goto out;
}
if (fprintf(f, "# %ld triangles\n", self->tri_num) < 0)
return -1;
for (i = 0; i < self->tri_num; i++) {
struct engine_mesh_face *face = &self->faces[i];
unsigned long uv_idx[3];
int j;
if (face->texture != last_texture) {
UObject *t = U_OBJECT(self->textures[face->texture]);
if (t && fprintf(f, "usemtl %s\n", t->pkg_name) < 0)
goto out;
last_texture = face->texture;
}
for (j = 0; j < 3; j++) {
if (uv_work) {
float (*result)[2];
result = bsearch(&face->uv[j], uv_work,
uv_num, sizeof uv_work[0],
self_uv_compar);
g_return_val_if_fail(result, -1);
uv_idx[j] = result - &uv_work[0] + 1;
} else {
uv_idx[j] = 3ul*i+j+1;
}
}
rc = fprintf(f, "f %lu/%lu %lu/%lu %lu/%lu\n",
face->verts[0]+1lu, uv_idx[0],
face->verts[1]+1lu, uv_idx[1],
face->verts[2]+1lu, uv_idx[2]);
if (rc < 0)
goto out;
}
ret = 0;
free(uv_work);
out:
return ret;
}
private int
deserialize_animations(Self *self, unsigned char *buf, size_t *bufsz)
{
struct upkg_file *f = U_OBJECT(self)->pkg_file;
size_t rc, buflen, pos = 0;
long i, j, anim_count;
buflen = upkg_export_read(f, buf, *bufsz);
pos += (rc = upkg_decode_index(&anim_count, buf, buflen));
if (rc == 0)
return -1;
for (i = 0; i < anim_count; i++) {
long x, nfuncs;
const char *s;
/* Name */
pos += (rc = upkg_decode_index(&x, buf+pos, buflen-pos));
if (rc == 0)
return -1;
s = upkg_get_name(f->pkg, x);
if (!s)
return -1;
/* Group */
pos += (rc = upkg_decode_index(&x, buf+pos, buflen-pos));
if (rc == 0)
return -1;
/* Start/end frame */
if (buflen-pos < 8)
return -1;
pos += 8;
/* Function count */
pos += (rc = upkg_decode_index(&nfuncs, buf+pos, buflen-pos));
if (rc == 0)
return -1;
for (j = 0; j < nfuncs; j++) {
if (buflen - pos < 13) {
buflen = self_fill_buf(f, buf, pos, buflen);
pos = 0;
}
/* time */
if (buflen-pos < 4)
return -1;
pos += 4;
/* function */
rc = upkg_decode_index(&x, buf+pos, buflen-pos);
if (rc == 0)
return -1;
pos += rc;
}
/* rate */
if (buflen-pos < 4)
return -1;
pos += 4;
buflen = self_fill_buf(f, buf, pos, buflen);
pos = 0;
}
if (pos != 0)
memmove(buf, buf+pos, buflen-pos);
*bufsz = buflen-pos;
return 0;
}
private int deserialize_textures(Self *self, unsigned char *buf, size_t bufsz)
{
struct upkg_file *f = U_OBJECT(self)->pkg_file;
size_t rc, buflen, pos = 0;
long i, idx;
buflen = upkg_export_read(f, buf, bufsz);
pos += (rc = upkg_decode_index(&idx, buf, buflen));
if (rc == 0 || idx < 0 || idx > SIZE_MAX / sizeof self->textures[0]) {
u_warn(self, "invalid texture count");
return -1;
}
self->num_textures = idx;
self->textures = malloc(self->num_textures * sizeof self->textures[0]);
if (!self->textures) {
u_warn(self, "failed to allocate memory");
return -1;
}
for (i = 0; i < self->num_textures; i++) {
GObject *obj = NULL;
if (buflen < 5) {
buflen = self_fill_buf(f, buf, pos, buflen);
pos = 0;
}
pos += (rc = upkg_decode_index(&idx, buf+pos, buflen-pos));
if (rc == 0) {
u_warn(self, "invalid texture reference");
} else if (idx == 0) {
/* Seems these lists start with null references, ignore... */
} else {
obj = u_object_get_by_link(G_OBJECT(self), idx);
if (!obj) {
u_warn(self, "failed to load texture %ld %ld", idx);
}
}
self->textures[i] = ENGINE_TEXTURE(obj);
}
if (upkg_export_seek(f, (long)pos - (long)buflen, SEEK_CUR) != 0)
return -1;
return 0;
}
override (U:Object) int deserialize(U:Object *uo)
{
Self *self = SELF(uo);
struct upkg_file *f = uo->pkg_file;
const size_t sphere_sz = 12 + 4 * (f->pkg->version >= 62);
size_t buflen, pos = 0;
long idx;
unsigned char buf[50];
PARENT_HANDLER(uo);
buflen = upkg_export_read(f, buf, sizeof buf);
/* Bounding box */
if (buflen - pos < 25)
return -1;
pos += 25;
/* Bounding sphere */
if (buflen - pos < sphere_sz)
return -1;
pos += sphere_sz;
self->vert_base = pos + 4*(f->pkg->version >= 61);
if (self_skip_array(uo, &self->vert_num, 4, buf+pos, buflen-pos) != 0) {
u_err(uo, "invalid vertex array");
return -1;
}
self->tri_base = f->offset + 4*(f->pkg->version >= 61);
if (self_skip_array(uo, &self->tri_num, 20, NULL, 0) != 0) {
u_err(uo, "invalid triangle array");
return -1;
}
if (self_deserialize_animations(self, buf, &buflen) != 0) {
return -1;
}
self->conn_base = f->offset - buflen;
if (self_skip_array(uo, &self->conn_num, 8, buf, buflen) != 0) {
u_err(uo, "invalid connection array");
return -1;
}
buflen = upkg_export_read(f, buf, sizeof buf);
pos = 0;
/* Another bounding box? */
if (buflen - pos < 25)
return -1;
pos += 25;
/* Another bounding sphere? */
if (buflen - pos < sphere_sz)
return -1;
pos += sphere_sz;
self->vlink_base = f->offset - (buflen - pos);
if (self_skip_array(uo, &self->vlink_num, 4, buf+pos, buflen-pos)) {
u_err(uo, "invalid vertex link array");
return -1;
}
if (self_deserialize_textures(self, buf, buflen) != 0) {
return -1;
}
/* More bounding boxes!? */
buflen = upkg_export_read(f, buf, 5);
pos = upkg_decode_index(&idx, buf, buflen);
if (pos == 0 || idx < 0 || idx > LONG_MAX / 25) {
u_err(uo, "invalid bounding box array");
return -1;
}
if (upkg_export_seek(f, (long)pos - 5 + idx * 25, SEEK_CUR) != 0)
return -1;
/* More bounding spheres!? */
buflen = upkg_export_read(f, buf, 5);
pos = upkg_decode_index(&idx, buf, buflen);
if (pos == 0 || idx < 0 || idx > LONG_MAX / 16) {
u_err(uo, "invalid bounding sphere array");
return -1;
}
if (upkg_export_seek(f, (long)pos - 5 + idx * sphere_sz, SEEK_CUR) != 0)
return -1;
self->_priv->pkg_file = *f;
return 0;
}
}