%alltop{ /* * upkg: tool for manipulating Unreal Tournament packages. * Copyright © 2020 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 "pack.h" %} %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; } }