1/* 2 * CDToons video decoder 3 * Copyright (C) 2020 Alyssa Milburn <amilburn@zall.org> 4 * 5 * This file is part of FFmpeg. 6 * 7 * FFmpeg is free software; you can redistribute it and/or 8 * modify it under the terms of the GNU Lesser General Public 9 * License as published by the Free Software Foundation; either 10 * version 2.1 of the License, or (at your option) any later version. 11 * 12 * FFmpeg is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 * Lesser General Public License for more details. 16 * 17 * You should have received a copy of the GNU Lesser General Public 18 * License along with FFmpeg; if not, write to the Free Software 19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 20 */ 21 22/** 23 * @file 24 * CDToons video decoder 25 * @author Alyssa Milburn <amilburn@zall.org> 26 */ 27 28#include <stdint.h> 29 30#include "libavutil/attributes.h" 31#include "libavutil/internal.h" 32#include "avcodec.h" 33#include "bytestream.h" 34#include "codec_internal.h" 35#include "internal.h" 36 37#define CDTOONS_HEADER_SIZE 44 38#define CDTOONS_MAX_SPRITES 1200 39 40typedef struct CDToonsSprite { 41 uint16_t flags; 42 uint16_t owner_frame; 43 uint16_t start_frame; 44 uint16_t end_frame; 45 unsigned int alloc_size; 46 uint32_t size; 47 uint8_t *data; 48 int active; 49} CDToonsSprite; 50 51typedef struct CDToonsContext { 52 AVFrame *frame; 53 54 uint16_t last_pal_id; ///< The index of the active palette sprite. 55 uint32_t pal[256]; ///< The currently-used palette data. 56 CDToonsSprite sprites[CDTOONS_MAX_SPRITES]; 57} CDToonsContext; 58 59static int cdtoons_render_sprite(AVCodecContext *avctx, const uint8_t *data, 60 uint32_t data_size, 61 int dst_x, int dst_y, int width, int height) 62{ 63 CDToonsContext *c = avctx->priv_data; 64 const uint8_t *next_line = data; 65 const uint8_t *end = data + data_size; 66 uint16_t line_size; 67 uint8_t *dest; 68 int skip = 0, to_skip, x; 69 70 if (dst_x + width > avctx->width) 71 width = avctx->width - dst_x; 72 if (dst_y + height > avctx->height) 73 height = avctx->height - dst_y; 74 75 if (dst_x < 0) { 76 /* we need to skip the start of the scanlines */ 77 skip = -dst_x; 78 if (width <= skip) 79 return 0; 80 dst_x = 0; 81 } 82 83 for (int y = 0; y < height; y++) { 84 /* one scanline at a time, size is provided */ 85 data = next_line; 86 if (end - data < 2) 87 return 1; 88 line_size = bytestream_get_be16(&data); 89 if (end - data < line_size) 90 return 1; 91 next_line = data + line_size; 92 if (dst_y + y < 0) 93 continue; 94 95 dest = c->frame->data[0] + (dst_y + y) * c->frame->linesize[0] + dst_x; 96 97 to_skip = skip; 98 x = 0; 99 while (x < width - skip) { 100 int raw, size, step; 101 uint8_t val; 102 103 if (data >= end) 104 return 1; 105 106 val = bytestream_get_byte(&data); 107 raw = !(val & 0x80); 108 size = (int)(val & 0x7F) + 1; 109 110 /* skip the start of a scanline if it is off-screen */ 111 if (to_skip >= size) { 112 to_skip -= size; 113 if (raw) { 114 step = size; 115 } else { 116 step = 1; 117 } 118 if (next_line - data < step) 119 return 1; 120 data += step; 121 continue; 122 } else if (to_skip) { 123 size -= to_skip; 124 if (raw) { 125 if (next_line - data < to_skip) 126 return 1; 127 data += to_skip; 128 } 129 to_skip = 0; 130 } 131 132 if (x + size >= width - skip) 133 size = width - skip - x; 134 135 /* either raw data, or a run of a single color */ 136 if (raw) { 137 if (next_line - data < size) 138 return 1; 139 memcpy(dest + x, data, size); 140 data += size; 141 } else { 142 uint8_t color = bytestream_get_byte(&data); 143 /* ignore transparent runs */ 144 if (color) 145 memset(dest + x, color, size); 146 } 147 x += size; 148 } 149 } 150 151 return 0; 152} 153 154static int cdtoons_decode_frame(AVCodecContext *avctx, AVFrame *rframe, 155 int *got_frame, AVPacket *avpkt) 156{ 157 CDToonsContext *c = avctx->priv_data; 158 const uint8_t *buf = avpkt->data; 159 const uint8_t *eod = avpkt->data + avpkt->size; 160 const int buf_size = avpkt->size; 161 uint16_t frame_id; 162 uint8_t background_color; 163 uint16_t sprite_count, sprite_offset; 164 uint8_t referenced_count; 165 uint16_t palette_id; 166 uint8_t palette_set; 167 int ret; 168 int saw_embedded_sprites = 0; 169 170 if (buf_size < CDTOONS_HEADER_SIZE) 171 return AVERROR_INVALIDDATA; 172 173 if ((ret = ff_reget_buffer(avctx, c->frame, 0)) < 0) 174 return ret; 175 176 /* a lot of the header is useless junk in the absence of 177 * dirty rectangling etc */ 178 buf += 2; /* version? (always 9?) */ 179 frame_id = bytestream_get_be16(&buf); 180 buf += 2; /* blocks_valid_until */ 181 buf += 1; 182 background_color = bytestream_get_byte(&buf); 183 buf += 16; /* clip rect, dirty rect */ 184 buf += 4; /* flags */ 185 sprite_count = bytestream_get_be16(&buf); 186 sprite_offset = bytestream_get_be16(&buf); 187 buf += 2; /* max block id? */ 188 referenced_count = bytestream_get_byte(&buf); 189 buf += 1; 190 palette_id = bytestream_get_be16(&buf); 191 palette_set = bytestream_get_byte(&buf); 192 buf += 5; 193 194 if (sprite_offset > buf_size) 195 return AVERROR_INVALIDDATA; 196 197 /* read new sprites introduced in this frame */ 198 buf = avpkt->data + sprite_offset; 199 while (sprite_count--) { 200 uint32_t size; 201 uint16_t sprite_id; 202 203 if (buf + 14 > eod) 204 return AVERROR_INVALIDDATA; 205 206 sprite_id = bytestream_get_be16(&buf); 207 if (sprite_id >= CDTOONS_MAX_SPRITES) { 208 av_log(avctx, AV_LOG_ERROR, 209 "Sprite ID %d is too high.\n", sprite_id); 210 return AVERROR_INVALIDDATA; 211 } 212 if (c->sprites[sprite_id].active) { 213 av_log(avctx, AV_LOG_ERROR, 214 "Sprite ID %d is a duplicate.\n", sprite_id); 215 return AVERROR_INVALIDDATA; 216 } 217 218 c->sprites[sprite_id].flags = bytestream_get_be16(&buf); 219 size = bytestream_get_be32(&buf); 220 if (size < 14) { 221 av_log(avctx, AV_LOG_ERROR, 222 "Sprite only has %d bytes of data.\n", size); 223 return AVERROR_INVALIDDATA; 224 } 225 size -= 14; 226 c->sprites[sprite_id].size = size; 227 c->sprites[sprite_id].owner_frame = frame_id; 228 c->sprites[sprite_id].start_frame = bytestream_get_be16(&buf); 229 c->sprites[sprite_id].end_frame = bytestream_get_be16(&buf); 230 buf += 2; 231 232 if (size > buf_size || buf + size > eod) 233 return AVERROR_INVALIDDATA; 234 235 av_fast_padded_malloc(&c->sprites[sprite_id].data, &c->sprites[sprite_id].alloc_size, size); 236 if (!c->sprites[sprite_id].data) 237 return AVERROR(ENOMEM); 238 239 c->sprites[sprite_id].active = 1; 240 241 bytestream_get_buffer(&buf, c->sprites[sprite_id].data, size); 242 } 243 244 /* render any embedded sprites */ 245 while (buf < eod) { 246 uint32_t tag, size; 247 if (buf + 8 > eod) { 248 av_log(avctx, AV_LOG_WARNING, "Ran (seriously) out of data for embedded sprites.\n"); 249 return AVERROR_INVALIDDATA; 250 } 251 tag = bytestream_get_be32(&buf); 252 size = bytestream_get_be32(&buf); 253 if (tag == MKBETAG('D', 'i', 'f', 'f')) { 254 uint16_t diff_count; 255 if (buf + 10 > eod) { 256 av_log(avctx, AV_LOG_WARNING, "Ran (seriously) out of data for Diff frame.\n"); 257 return AVERROR_INVALIDDATA; 258 } 259 diff_count = bytestream_get_be16(&buf); 260 buf += 8; /* clip rect? */ 261 for (int i = 0; i < diff_count; i++) { 262 int16_t top, left; 263 uint16_t diff_size, width, height; 264 265 if (buf + 16 > eod) { 266 av_log(avctx, AV_LOG_WARNING, "Ran (seriously) out of data for Diff frame header.\n"); 267 return AVERROR_INVALIDDATA; 268 } 269 270 top = bytestream_get_be16(&buf); 271 left = bytestream_get_be16(&buf); 272 buf += 4; /* bottom, right */ 273 diff_size = bytestream_get_be32(&buf); 274 width = bytestream_get_be16(&buf); 275 height = bytestream_get_be16(&buf); 276 if (diff_size < 8 || diff_size - 4 > eod - buf) { 277 av_log(avctx, AV_LOG_WARNING, "Ran (seriously) out of data for Diff frame data.\n"); 278 return AVERROR_INVALIDDATA; 279 } 280 if (cdtoons_render_sprite(avctx, buf + 4, diff_size - 8, 281 left, top, width, height)) { 282 av_log(avctx, AV_LOG_WARNING, "Ran beyond end of sprite while rendering.\n"); 283 } 284 buf += diff_size - 4; 285 } 286 saw_embedded_sprites = 1; 287 } else { 288 /* we don't care about any other entries */ 289 if (size < 8 || size - 8 > eod - buf) { 290 av_log(avctx, AV_LOG_WARNING, "Ran out of data for ignored entry (size %X, %d left).\n", size, (int)(eod - buf)); 291 return AVERROR_INVALIDDATA; 292 } 293 buf += (size - 8); 294 } 295 } 296 297 /* was an intra frame? */ 298 if (saw_embedded_sprites) 299 goto done; 300 301 /* render any referenced sprites */ 302 buf = avpkt->data + CDTOONS_HEADER_SIZE; 303 eod = avpkt->data + sprite_offset; 304 for (int i = 0; i < referenced_count; i++) { 305 const uint8_t *block_data; 306 uint16_t sprite_id, width, height; 307 int16_t top, left, right; 308 309 if (buf + 10 > eod) { 310 av_log(avctx, AV_LOG_WARNING, "Ran (seriously) out of data when rendering.\n"); 311 return AVERROR_INVALIDDATA; 312 } 313 314 sprite_id = bytestream_get_be16(&buf); 315 top = bytestream_get_be16(&buf); 316 left = bytestream_get_be16(&buf); 317 buf += 2; /* bottom */ 318 right = bytestream_get_be16(&buf); 319 320 if ((i == 0) && (sprite_id == 0)) { 321 /* clear background */ 322 memset(c->frame->data[0], background_color, 323 c->frame->linesize[0] * avctx->height); 324 } 325 326 if (!right) 327 continue; 328 if (sprite_id >= CDTOONS_MAX_SPRITES) { 329 av_log(avctx, AV_LOG_ERROR, 330 "Sprite ID %d is too high.\n", sprite_id); 331 return AVERROR_INVALIDDATA; 332 } 333 334 block_data = c->sprites[sprite_id].data; 335 if (!c->sprites[sprite_id].active) { 336 /* this can happen when seeking around */ 337 av_log(avctx, AV_LOG_WARNING, "Sprite %d is missing.\n", sprite_id); 338 continue; 339 } 340 if (c->sprites[sprite_id].size < 14) { 341 av_log(avctx, AV_LOG_ERROR, "Sprite %d is too small.\n", sprite_id); 342 continue; 343 } 344 345 height = bytestream_get_be16(&block_data); 346 width = bytestream_get_be16(&block_data); 347 block_data += 10; 348 if (cdtoons_render_sprite(avctx, block_data, 349 c->sprites[sprite_id].size - 14, 350 left, top, width, height)) { 351 av_log(avctx, AV_LOG_WARNING, "Ran beyond end of sprite while rendering.\n"); 352 } 353 } 354 355 if (palette_id && (palette_id != c->last_pal_id)) { 356 if (palette_id >= CDTOONS_MAX_SPRITES) { 357 av_log(avctx, AV_LOG_ERROR, 358 "Palette ID %d is too high.\n", palette_id); 359 return AVERROR_INVALIDDATA; 360 } 361 if (!c->sprites[palette_id].active) { 362 /* this can happen when seeking around */ 363 av_log(avctx, AV_LOG_WARNING, 364 "Palette ID %d is missing.\n", palette_id); 365 goto done; 366 } 367 if (c->sprites[palette_id].size != 256 * 2 * 3) { 368 av_log(avctx, AV_LOG_ERROR, 369 "Palette ID %d is wrong size (%d).\n", 370 palette_id, c->sprites[palette_id].size); 371 return AVERROR_INVALIDDATA; 372 } 373 c->last_pal_id = palette_id; 374 if (!palette_set) { 375 uint8_t *palette_data = c->sprites[palette_id].data; 376 for (int i = 0; i < 256; i++) { 377 /* QuickTime-ish palette: 16-bit RGB components */ 378 unsigned r, g, b; 379 r = *palette_data; 380 g = *(palette_data + 2); 381 b = *(palette_data + 4); 382 c->pal[i] = (0xFFU << 24) | (r << 16) | (g << 8) | b; 383 palette_data += 6; 384 } 385 /* first palette entry indicates transparency */ 386 c->pal[0] = 0; 387 c->frame->palette_has_changed = 1; 388 } 389 } 390 391done: 392 /* discard outdated blocks */ 393 for (int i = 0; i < CDTOONS_MAX_SPRITES; i++) { 394 if (c->sprites[i].end_frame > frame_id) 395 continue; 396 c->sprites[i].active = 0; 397 } 398 399 memcpy(c->frame->data[1], c->pal, AVPALETTE_SIZE); 400 401 if ((ret = av_frame_ref(rframe, c->frame)) < 0) 402 return ret; 403 404 *got_frame = 1; 405 406 /* always report that the buffer was completely consumed */ 407 return buf_size; 408} 409 410static av_cold int cdtoons_decode_init(AVCodecContext *avctx) 411{ 412 CDToonsContext *c = avctx->priv_data; 413 414 avctx->pix_fmt = AV_PIX_FMT_PAL8; 415 c->last_pal_id = 0; 416 c->frame = av_frame_alloc(); 417 if (!c->frame) 418 return AVERROR(ENOMEM); 419 420 return 0; 421} 422 423static void cdtoons_flush(AVCodecContext *avctx) 424{ 425 CDToonsContext *c = avctx->priv_data; 426 427 c->last_pal_id = 0; 428 for (int i = 0; i < CDTOONS_MAX_SPRITES; i++) 429 c->sprites[i].active = 0; 430} 431 432static av_cold int cdtoons_decode_end(AVCodecContext *avctx) 433{ 434 CDToonsContext *c = avctx->priv_data; 435 436 for (int i = 0; i < CDTOONS_MAX_SPRITES; i++) { 437 av_freep(&c->sprites[i].data); 438 c->sprites[i].active = 0; 439 } 440 441 av_frame_free(&c->frame); 442 443 return 0; 444} 445 446const FFCodec ff_cdtoons_decoder = { 447 .p.name = "cdtoons", 448 .p.long_name = NULL_IF_CONFIG_SMALL("CDToons video"), 449 .p.type = AVMEDIA_TYPE_VIDEO, 450 .p.id = AV_CODEC_ID_CDTOONS, 451 .priv_data_size = sizeof(CDToonsContext), 452 .init = cdtoons_decode_init, 453 .close = cdtoons_decode_end, 454 FF_CODEC_DECODE_CB(cdtoons_decode_frame), 455 .p.capabilities = AV_CODEC_CAP_DR1, 456 .flush = cdtoons_flush, 457 .caps_internal = FF_CODEC_CAP_INIT_THREADSAFE, 458}; 459