From 2d1b8dad3a0743e5e2d126fe6a2d29ad2d027a44 Mon Sep 17 00:00:00 2001 From: Nunuhara Cabbage Date: Sat, 7 Mar 2020 21:44:15 -0800 Subject: [PATCH] Partial AJP CG support Support for AJP without alpha channel. --- README.md | 3 + include/system4/ajp.h | 29 +++++++++ include/system4/qnt.h | 4 +- meson.build | 1 + src/ajp.c | 137 ++++++++++++++++++++++++++++++++++++++++++ src/cg.c | 10 ++- src/meson.build | 3 +- src/qnt.c | 8 +-- 8 files changed, 187 insertions(+), 8 deletions(-) create mode 100644 include/system4/ajp.h create mode 100644 src/ajp.c diff --git a/README.md b/README.md index bef0bd9..fa0993d 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,11 @@ Building First install the dependencies (corresponding Debian package in parentheses): * chibi-scheme [optional, for debugger] +* bison (bison) +* flex (flex) * meson (meson) * libffi (libffi-dev) +* libturbojpeg (libturbojpeg0-dev) * SDL2 (libsdl2-dev) * SDL2_mixer (libsdl2-mixer-dev) * SDL2_ttf (libsdl2-ttf-dev) diff --git a/include/system4/ajp.h b/include/system4/ajp.h new file mode 100644 index 0000000..0ce94a7 --- /dev/null +++ b/include/system4/ajp.h @@ -0,0 +1,29 @@ +/* Copyright (C) 2019 Nunuhara Cabbage + * + * 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 2 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 SYSTEM4_AJP_H +#define SYSTEM4_AJP_H + +#include +#include +#include + +struct cg; + +bool ajp_checkfmt(const uint8_t *data); +void ajp_extract(uint8_t *data, size_t size, struct cg *cg); + +#endif /* SYSTEM4_AJP_H */ diff --git a/include/system4/qnt.h b/include/system4/qnt.h index a8cb1af..231ab1c 100644 --- a/include/system4/qnt.h +++ b/include/system4/qnt.h @@ -44,8 +44,8 @@ struct qnt_header { int alpha_size; // compressed alpha pixel size }; -bool qnt_checkfmt(uint8_t *data); +bool qnt_checkfmt(const uint8_t *data); bool qnt_get_metrics(uint8_t *data, struct cg_metrics *dst); void qnt_extract(uint8_t *data, struct cg *cg); -#endif /* __QNT_H__ */ +#endif /* SYSTEM4_QNT_H */ diff --git a/meson.build b/meson.build index 908acfe..386746a 100644 --- a/meson.build +++ b/meson.build @@ -10,6 +10,7 @@ sdl2mixer = dependency('SDL2_mixer') ffi = dependency('libffi') gl = dependency('GL') glew = dependency('glew') +tj = dependency('libturbojpeg') chibi = dependency('chibi-scheme', required : get_option('debugger')) incdir = include_directories('include') diff --git a/src/ajp.c b/src/ajp.c new file mode 100644 index 0000000..169dd50 --- /dev/null +++ b/src/ajp.c @@ -0,0 +1,137 @@ +/* Copyright (C) 2019 Nunuhara Cabbage + * + * 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 2 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 "little_endian.h" +#include "system4.h" +#include "system4/cg.h" + +bool ajp_checkfmt(const uint8_t *data) +{ + if (data[0] != 'A' || data[1] != 'J' || data[2] != 'P' || data[3] != '\0') + return false; + return true; +} + +static const uint8_t ajp_key[] = { + 0x5d, 0x91, 0xae, 0x87, + 0x4a, 0x56, 0x51, 0xcd, + 0x83, 0xec, 0x4c, 0x92, + 0xb5, 0xcb, 0x16, 0x34 +}; + +static void ajp_decrypt(uint8_t *data, size_t size) +{ + for (size_t i = 0; i < 16 && i < size; i++) { + data[i] ^= ajp_key[i]; + } +} + +struct ajp_header { + uint32_t width; + uint32_t height; + uint32_t jpeg_off; + uint32_t jpeg_size; + uint32_t mask_off; + uint32_t mask_size; +}; + +static void ajp_extract_header(uint8_t *data, struct ajp_header *ajp) +{ + ajp->width = LittleEndian_getDW(data, 12); + ajp->height = LittleEndian_getDW(data, 16); + ajp->jpeg_off = LittleEndian_getDW(data, 20); + ajp->jpeg_size = LittleEndian_getDW(data, 24); + ajp->mask_off = LittleEndian_getDW(data, 28); + ajp->mask_size = LittleEndian_getDW(data, 32); +} + +static void ajp_init_metrics(struct ajp_header *ajp, struct cg_metrics *dst) +{ + dst->w = ajp->width; + dst->h = ajp->height; + dst->bpp = 24; + dst->has_pixel = ajp->jpeg_size > 0; + //dst->has_alpha = ajp->mask_size > 0; + dst->has_alpha = false; + dst->pixel_pitch = ajp->width * 3; + dst->alpha_pitch = 1; +} + +void ajp_extract(uint8_t *data, size_t size, struct cg *cg) +{ + uint8_t *buf = NULL, *jpeg_data = NULL, *mask_data = NULL; + int width, height, subsamp; + struct ajp_header ajp; + ajp_extract_header(data, &ajp); + ajp_init_metrics(&ajp, &cg->metrics); + + if (ajp.jpeg_off > size) + ERROR("AJP JPEG offset invalid"); + if (ajp.jpeg_off + ajp.jpeg_size > size) + ERROR("AJP JPEG size invalid"); + if (ajp.mask_off > size) + ERROR("AJP mask offset invalid"); + if (ajp.mask_off + ajp.mask_size > size) + ERROR("AJP mask size invalid"); + + jpeg_data = xmalloc(ajp.jpeg_size); + mask_data = xmalloc(ajp.mask_size); + memcpy(jpeg_data, data + ajp.jpeg_off, ajp.jpeg_size); + memcpy(mask_data, data + ajp.mask_off, ajp.mask_size); + ajp_decrypt(jpeg_data, ajp.jpeg_size); + ajp_decrypt(mask_data, ajp.mask_size); + + if (ajp.mask_size) { + WARNING("AJP mask not implemented"); + } + + tjhandle decompressor = tjInitDecompress(); + tjDecompressHeader2(decompressor, jpeg_data, ajp.jpeg_size, &width, &height, &subsamp); + if ((uint32_t)width != ajp.width) + WARNING("AJP width doesn't match JPEG width (%d vs. %u)", width, ajp.width); + if ((uint32_t)height != ajp.height) + WARNING("AJP height doesn't match JPEG height (%d vs. %u)", height, ajp.height); + + buf = xmalloc(width * height * 3); + if (tjDecompress2(decompressor, jpeg_data, ajp.jpeg_size, buf, width, 0, height, TJPF_RGB, TJFLAG_FASTDCT) < 0) { + WARNING("JPEG decompression failed: %s", tjGetErrorStr()); + free(buf); + goto cleanup; + } + + // XXX: add solid alpha mask + uint8_t *tmp = xmalloc(width * height * 4); + for (int i = 0; i < width * height; i++) { + tmp[i*4+0] = buf[i*3+0]; + tmp[i*4+1] = buf[i*3+1]; + tmp[i*4+2] = buf[i*3+2]; + tmp[i*4+3] = 0xFF; + } + free(buf); + buf = tmp; + + cg->type = ALCG_AJP; + cg->pixels = buf; + +cleanup: + free(jpeg_data); + free(mask_data); + tjDestroy(decompressor); +} diff --git a/src/cg.c b/src/cg.c index 0d793f5..a9ee255 100644 --- a/src/cg.c +++ b/src/cg.c @@ -20,6 +20,7 @@ #include "system4.h" #include "system4/archive.h" #include "system4/cg.h" +#include "system4/ajp.h" #include "system4/qnt.h" /* @@ -31,6 +32,8 @@ static enum cg_type check_cgformat(uint8_t *data) { if (qnt_checkfmt(data)) { return ALCG_QNT; + } else if (ajp_checkfmt(data)) { + return ALCG_AJP; } return ALCG_UNKNOWN; } @@ -46,6 +49,11 @@ bool cg_get_metrics(struct archive *ar, int no, struct cg_metrics *dst) case ALCG_QNT: qnt_get_metrics(dfile->data, dst); break; + case ALCG_AJP: + WARNING("AJP GetMetrics not implemented"); + archive_free_data(dfile); + return false; + break; default: WARNING("Unknown CG type (CG %d)", no); archive_free_data(dfile); @@ -93,7 +101,7 @@ struct cg *cg_load(struct archive *ar, int no) qnt_extract(dfile->data, cg); break; case ALCG_AJP: - WARNING("Unimplemented CG type: AJP"); + ajp_extract(dfile->data, dfile->size, cg); break; case ALCG_PNG: WARNING("Unimplemented CG type: PNG"); diff --git a/src/meson.build b/src/meson.build index 5d44f3b..e951c97 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,6 +1,7 @@ # sources for libsys4.a system4 = ['afa.c', 'ain.c', + 'ajp.c', 'ald.c', 'archive.c', 'buffer.c', @@ -58,7 +59,7 @@ if chibi.found() endif libsys4 = static_library('sys4', system4, - dependencies : [libm, zlib], + dependencies : [libm, zlib, tj], include_directories : incdir) executable('xsystem4', xsystem4, diff --git a/src/qnt.c b/src/qnt.c index 570ac2e..54b90d9 100644 --- a/src/qnt.c +++ b/src/qnt.c @@ -221,7 +221,7 @@ static void extract_alpha(struct qnt_header *qnt, uint8_t *pic, uint8_t *b) * * return: true if data is qnt */ -bool qnt_checkfmt(uint8_t *data) +bool qnt_checkfmt(const uint8_t *data) { if (data[0] != 'Q' || data[1] != 'N' || data[2] != 'T') return false; return true; @@ -259,14 +259,14 @@ void qnt_extract(uint8_t *data, struct cg *cg) extract_header(data, &qnt); qnt_init_metrics(&qnt, &cg->metrics); - uint8_t *pixels = calloc(1, sizeof(uint8_t) * ((qnt.width+10) * (qnt.height+10) * 3)); + uint8_t *pixels = xcalloc(3, (qnt.width+10) * (qnt.height+10)); extract_pixel(&qnt, pixels, data + qnt.hdr_size); cg->type = ALCG_QNT; // combine color/alpha data - uint8_t *alpha = malloc(sizeof(uint8_t) * ((qnt.width+10) * (qnt.height+10))); - uint8_t *tmp = malloc((qnt.width+10) * (qnt.height+10) * 4); + uint8_t *alpha = xmalloc((qnt.width+10) * (qnt.height+10)); + uint8_t *tmp = xmalloc((qnt.width+10) * (qnt.height+10) * 4); if (qnt.alpha_size) { extract_alpha(&qnt, alpha, data + qnt.hdr_size + qnt.pixel_size); } else {