/src/ffmpeg/libavcodec/cdtoons.c
Line | Count | Source (jump to first uncovered line) |
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 "libavutil/mem.h" |
33 | | #include "avcodec.h" |
34 | | #include "bytestream.h" |
35 | | #include "codec_internal.h" |
36 | | #include "decode.h" |
37 | | |
38 | 230k | #define CDTOONS_HEADER_SIZE 44 |
39 | 200M | #define CDTOONS_MAX_SPRITES 1200 |
40 | | |
41 | | typedef struct CDToonsSprite { |
42 | | uint16_t flags; |
43 | | uint16_t owner_frame; |
44 | | uint16_t start_frame; |
45 | | uint16_t end_frame; |
46 | | unsigned int alloc_size; |
47 | | uint32_t size; |
48 | | uint8_t *data; |
49 | | int active; |
50 | | } CDToonsSprite; |
51 | | |
52 | | typedef struct CDToonsContext { |
53 | | AVFrame *frame; |
54 | | |
55 | | uint16_t last_pal_id; ///< The index of the active palette sprite. |
56 | | uint32_t pal[256]; ///< The currently-used palette data. |
57 | | CDToonsSprite sprites[CDTOONS_MAX_SPRITES]; |
58 | | } CDToonsContext; |
59 | | |
60 | | static int cdtoons_render_sprite(AVCodecContext *avctx, const uint8_t *data, |
61 | | uint32_t data_size, |
62 | | int dst_x, int dst_y, int width, int height) |
63 | 4.21k | { |
64 | 4.21k | CDToonsContext *c = avctx->priv_data; |
65 | 4.21k | const uint8_t *next_line = data; |
66 | 4.21k | const uint8_t *end = data + data_size; |
67 | 4.21k | uint16_t line_size; |
68 | 4.21k | uint8_t *dest; |
69 | 4.21k | int skip = 0, to_skip, x; |
70 | | |
71 | 4.21k | if (dst_x + width > avctx->width) |
72 | 2.85k | width = avctx->width - dst_x; |
73 | 4.21k | if (dst_y + height > avctx->height) |
74 | 1.96k | height = avctx->height - dst_y; |
75 | | |
76 | 4.21k | if (dst_x < 0) { |
77 | | /* we need to skip the start of the scanlines */ |
78 | 2.06k | skip = -dst_x; |
79 | 2.06k | if (width <= skip) |
80 | 304 | return 0; |
81 | 1.75k | dst_x = 0; |
82 | 1.75k | } |
83 | | |
84 | 22.0k | for (int y = 0; y < height; y++) { |
85 | | /* one scanline at a time, size is provided */ |
86 | 21.0k | data = next_line; |
87 | 21.0k | if (end - data < 2) |
88 | 257 | return 1; |
89 | 20.7k | line_size = bytestream_get_be16(&data); |
90 | 20.7k | if (end - data < line_size) |
91 | 1.05k | return 1; |
92 | 19.6k | next_line = data + line_size; |
93 | 19.6k | if (dst_y + y < 0) |
94 | 700 | continue; |
95 | | |
96 | 18.9k | dest = c->frame->data[0] + (dst_y + y) * c->frame->linesize[0] + dst_x; |
97 | | |
98 | 18.9k | to_skip = skip; |
99 | 18.9k | x = 0; |
100 | 46.0k | while (x < width - skip) { |
101 | 28.6k | int raw, size, step; |
102 | 28.6k | uint8_t val; |
103 | | |
104 | 28.6k | if (data >= end) |
105 | 237 | return 1; |
106 | | |
107 | 28.3k | val = bytestream_get_byte(&data); |
108 | 28.3k | raw = !(val & 0x80); |
109 | 28.3k | size = (int)(val & 0x7F) + 1; |
110 | | |
111 | | /* skip the start of a scanline if it is off-screen */ |
112 | 28.3k | if (to_skip >= size) { |
113 | 17.8k | to_skip -= size; |
114 | 17.8k | if (raw) { |
115 | 13.5k | step = size; |
116 | 13.5k | } else { |
117 | 4.33k | step = 1; |
118 | 4.33k | } |
119 | 17.8k | if (next_line - data < step) |
120 | 703 | return 1; |
121 | 17.1k | data += step; |
122 | 17.1k | continue; |
123 | 17.8k | } else if (to_skip) { |
124 | 1.10k | size -= to_skip; |
125 | 1.10k | if (raw) { |
126 | 594 | if (next_line - data < to_skip) |
127 | 218 | return 1; |
128 | 376 | data += to_skip; |
129 | 376 | } |
130 | 885 | to_skip = 0; |
131 | 885 | } |
132 | | |
133 | 10.2k | if (x + size >= width - skip) |
134 | 1.37k | size = width - skip - x; |
135 | | |
136 | | /* either raw data, or a run of a single color */ |
137 | 10.2k | if (raw) { |
138 | 5.80k | if (next_line - data < size) |
139 | 431 | return 1; |
140 | 5.37k | memcpy(dest + x, data, size); |
141 | 5.37k | data += size; |
142 | 5.37k | } else { |
143 | 4.47k | uint8_t color = bytestream_get_byte(&data); |
144 | | /* ignore transparent runs */ |
145 | 4.47k | if (color) |
146 | 3.12k | memset(dest + x, color, size); |
147 | 4.47k | } |
148 | 9.84k | x += size; |
149 | 9.84k | } |
150 | 18.9k | } |
151 | | |
152 | 1.01k | return 0; |
153 | 3.90k | } |
154 | | |
155 | | static int cdtoons_decode_frame(AVCodecContext *avctx, AVFrame *rframe, |
156 | | int *got_frame, AVPacket *avpkt) |
157 | 132k | { |
158 | 132k | CDToonsContext *c = avctx->priv_data; |
159 | 132k | const uint8_t *buf = avpkt->data; |
160 | 132k | const uint8_t *eod = avpkt->data + avpkt->size; |
161 | 132k | const int buf_size = avpkt->size; |
162 | 132k | uint16_t frame_id; |
163 | 132k | uint8_t background_color; |
164 | 132k | uint16_t sprite_count, sprite_offset; |
165 | 132k | uint8_t referenced_count; |
166 | 132k | uint16_t palette_id; |
167 | 132k | uint8_t palette_set; |
168 | 132k | int ret; |
169 | 132k | int saw_embedded_sprites = 0; |
170 | | |
171 | 132k | if (buf_size < CDTOONS_HEADER_SIZE) |
172 | 20.8k | return AVERROR_INVALIDDATA; |
173 | | |
174 | 111k | if ((ret = ff_reget_buffer(avctx, c->frame, 0)) < 0) |
175 | 1.78k | return ret; |
176 | | |
177 | | /* a lot of the header is useless junk in the absence of |
178 | | * dirty rectangling etc */ |
179 | 109k | buf += 2; /* version? (always 9?) */ |
180 | 109k | frame_id = bytestream_get_be16(&buf); |
181 | 109k | buf += 2; /* blocks_valid_until */ |
182 | 109k | buf += 1; |
183 | 109k | background_color = bytestream_get_byte(&buf); |
184 | 109k | buf += 16; /* clip rect, dirty rect */ |
185 | 109k | buf += 4; /* flags */ |
186 | 109k | sprite_count = bytestream_get_be16(&buf); |
187 | 109k | sprite_offset = bytestream_get_be16(&buf); |
188 | 109k | buf += 2; /* max block id? */ |
189 | 109k | referenced_count = bytestream_get_byte(&buf); |
190 | 109k | buf += 1; |
191 | 109k | palette_id = bytestream_get_be16(&buf); |
192 | 109k | palette_set = bytestream_get_byte(&buf); |
193 | 109k | buf += 5; |
194 | | |
195 | 109k | if (sprite_offset > buf_size) |
196 | 2.33k | return AVERROR_INVALIDDATA; |
197 | | |
198 | | /* read new sprites introduced in this frame */ |
199 | 107k | buf = avpkt->data + sprite_offset; |
200 | 107k | while (sprite_count--) { |
201 | 3.20k | uint32_t size; |
202 | 3.20k | uint16_t sprite_id; |
203 | | |
204 | 3.20k | if (buf + 14 > eod) |
205 | 580 | return AVERROR_INVALIDDATA; |
206 | | |
207 | 2.62k | sprite_id = bytestream_get_be16(&buf); |
208 | 2.62k | if (sprite_id >= CDTOONS_MAX_SPRITES) { |
209 | 692 | av_log(avctx, AV_LOG_ERROR, |
210 | 692 | "Sprite ID %d is too high.\n", sprite_id); |
211 | 692 | return AVERROR_INVALIDDATA; |
212 | 692 | } |
213 | 1.92k | if (c->sprites[sprite_id].active) { |
214 | 288 | av_log(avctx, AV_LOG_ERROR, |
215 | 288 | "Sprite ID %d is a duplicate.\n", sprite_id); |
216 | 288 | return AVERROR_INVALIDDATA; |
217 | 288 | } |
218 | | |
219 | 1.64k | c->sprites[sprite_id].flags = bytestream_get_be16(&buf); |
220 | 1.64k | size = bytestream_get_be32(&buf); |
221 | 1.64k | if (size < 14) { |
222 | 245 | av_log(avctx, AV_LOG_ERROR, |
223 | 245 | "Sprite only has %d bytes of data.\n", size); |
224 | 245 | return AVERROR_INVALIDDATA; |
225 | 245 | } |
226 | 1.39k | size -= 14; |
227 | 1.39k | c->sprites[sprite_id].size = size; |
228 | 1.39k | c->sprites[sprite_id].owner_frame = frame_id; |
229 | 1.39k | c->sprites[sprite_id].start_frame = bytestream_get_be16(&buf); |
230 | 1.39k | c->sprites[sprite_id].end_frame = bytestream_get_be16(&buf); |
231 | 1.39k | buf += 2; |
232 | | |
233 | 1.39k | if (size > buf_size || buf + size > eod) |
234 | 692 | return AVERROR_INVALIDDATA; |
235 | | |
236 | 704 | av_fast_padded_malloc(&c->sprites[sprite_id].data, &c->sprites[sprite_id].alloc_size, size); |
237 | 704 | if (!c->sprites[sprite_id].data) |
238 | 0 | return AVERROR(ENOMEM); |
239 | | |
240 | 704 | c->sprites[sprite_id].active = 1; |
241 | | |
242 | 704 | bytestream_get_buffer(&buf, c->sprites[sprite_id].data, size); |
243 | 704 | } |
244 | | |
245 | | /* render any embedded sprites */ |
246 | 202k | while (buf < eod) { |
247 | 103k | uint32_t tag, size; |
248 | 103k | if (buf + 8 > eod) { |
249 | 422 | av_log(avctx, AV_LOG_WARNING, "Ran (seriously) out of data for embedded sprites.\n"); |
250 | 422 | return AVERROR_INVALIDDATA; |
251 | 422 | } |
252 | 103k | tag = bytestream_get_be32(&buf); |
253 | 103k | size = bytestream_get_be32(&buf); |
254 | 103k | if (tag == MKBETAG('D', 'i', 'f', 'f')) { |
255 | 2.60k | uint16_t diff_count; |
256 | 2.60k | if (buf + 10 > eod) { |
257 | 201 | av_log(avctx, AV_LOG_WARNING, "Ran (seriously) out of data for Diff frame.\n"); |
258 | 201 | return AVERROR_INVALIDDATA; |
259 | 201 | } |
260 | 2.40k | diff_count = bytestream_get_be16(&buf); |
261 | 2.40k | buf += 8; /* clip rect? */ |
262 | 3.23k | for (int i = 0; i < diff_count; i++) { |
263 | 2.51k | int16_t top, left; |
264 | 2.51k | uint16_t diff_size, width, height; |
265 | | |
266 | 2.51k | if (buf + 16 > eod) { |
267 | 239 | av_log(avctx, AV_LOG_WARNING, "Ran (seriously) out of data for Diff frame header.\n"); |
268 | 239 | return AVERROR_INVALIDDATA; |
269 | 239 | } |
270 | | |
271 | 2.27k | top = bytestream_get_be16(&buf); |
272 | 2.27k | left = bytestream_get_be16(&buf); |
273 | 2.27k | buf += 4; /* bottom, right */ |
274 | 2.27k | diff_size = bytestream_get_be32(&buf); |
275 | 2.27k | width = bytestream_get_be16(&buf); |
276 | 2.27k | height = bytestream_get_be16(&buf); |
277 | 2.27k | if (diff_size < 8 || diff_size - 4 > eod - buf) { |
278 | 1.44k | av_log(avctx, AV_LOG_WARNING, "Ran (seriously) out of data for Diff frame data.\n"); |
279 | 1.44k | return AVERROR_INVALIDDATA; |
280 | 1.44k | } |
281 | 835 | if (cdtoons_render_sprite(avctx, buf + 4, diff_size - 8, |
282 | 835 | left, top, width, height)) { |
283 | 385 | av_log(avctx, AV_LOG_WARNING, "Ran beyond end of sprite while rendering.\n"); |
284 | 385 | } |
285 | 835 | buf += diff_size - 4; |
286 | 835 | } |
287 | 723 | saw_embedded_sprites = 1; |
288 | 100k | } else { |
289 | | /* we don't care about any other entries */ |
290 | 100k | if (size < 8 || size - 8 > eod - buf) { |
291 | 3.07k | av_log(avctx, AV_LOG_WARNING, "Ran out of data for ignored entry (size %X, %d left).\n", size, (int)(eod - buf)); |
292 | 3.07k | return AVERROR_INVALIDDATA; |
293 | 3.07k | } |
294 | 97.5k | buf += (size - 8); |
295 | 97.5k | } |
296 | 103k | } |
297 | | |
298 | | /* was an intra frame? */ |
299 | 99.1k | if (saw_embedded_sprites) |
300 | 228 | goto done; |
301 | | |
302 | | /* render any referenced sprites */ |
303 | 98.9k | buf = avpkt->data + CDTOONS_HEADER_SIZE; |
304 | 98.9k | eod = avpkt->data + sprite_offset; |
305 | 106k | for (int i = 0; i < referenced_count; i++) { |
306 | 8.56k | const uint8_t *block_data; |
307 | 8.56k | uint16_t sprite_id, width, height; |
308 | 8.56k | int16_t top, left, right; |
309 | | |
310 | 8.56k | if (buf + 10 > eod) { |
311 | 255 | av_log(avctx, AV_LOG_WARNING, "Ran (seriously) out of data when rendering.\n"); |
312 | 255 | return AVERROR_INVALIDDATA; |
313 | 255 | } |
314 | | |
315 | 8.30k | sprite_id = bytestream_get_be16(&buf); |
316 | 8.30k | top = bytestream_get_be16(&buf); |
317 | 8.30k | left = bytestream_get_be16(&buf); |
318 | 8.30k | buf += 2; /* bottom */ |
319 | 8.30k | right = bytestream_get_be16(&buf); |
320 | | |
321 | 8.30k | if ((i == 0) && (sprite_id == 0)) { |
322 | | /* clear background */ |
323 | 595 | memset(c->frame->data[0], background_color, |
324 | 595 | c->frame->linesize[0] * avctx->height); |
325 | 595 | } |
326 | | |
327 | 8.30k | if (!right) |
328 | 2.60k | continue; |
329 | 5.70k | if (sprite_id >= CDTOONS_MAX_SPRITES) { |
330 | 326 | av_log(avctx, AV_LOG_ERROR, |
331 | 326 | "Sprite ID %d is too high.\n", sprite_id); |
332 | 326 | return AVERROR_INVALIDDATA; |
333 | 326 | } |
334 | | |
335 | 5.37k | block_data = c->sprites[sprite_id].data; |
336 | 5.37k | if (!c->sprites[sprite_id].active) { |
337 | | /* this can happen when seeking around */ |
338 | 1.79k | av_log(avctx, AV_LOG_WARNING, "Sprite %d is missing.\n", sprite_id); |
339 | 1.79k | continue; |
340 | 1.79k | } |
341 | 3.58k | if (c->sprites[sprite_id].size < 14) { |
342 | 204 | av_log(avctx, AV_LOG_ERROR, "Sprite %d is too small.\n", sprite_id); |
343 | 204 | continue; |
344 | 204 | } |
345 | | |
346 | 3.37k | height = bytestream_get_be16(&block_data); |
347 | 3.37k | width = bytestream_get_be16(&block_data); |
348 | 3.37k | block_data += 10; |
349 | 3.37k | if (cdtoons_render_sprite(avctx, block_data, |
350 | 3.37k | c->sprites[sprite_id].size - 14, |
351 | 3.37k | left, top, width, height)) { |
352 | 2.51k | av_log(avctx, AV_LOG_WARNING, "Ran beyond end of sprite while rendering.\n"); |
353 | 2.51k | } |
354 | 3.37k | } |
355 | | |
356 | 98.3k | if (palette_id && (palette_id != c->last_pal_id)) { |
357 | 97.8k | if (palette_id >= CDTOONS_MAX_SPRITES) { |
358 | 482 | av_log(avctx, AV_LOG_ERROR, |
359 | 482 | "Palette ID %d is too high.\n", palette_id); |
360 | 482 | return AVERROR_INVALIDDATA; |
361 | 482 | } |
362 | 97.3k | if (!c->sprites[palette_id].active) { |
363 | | /* this can happen when seeking around */ |
364 | 95.2k | av_log(avctx, AV_LOG_WARNING, |
365 | 95.2k | "Palette ID %d is missing.\n", palette_id); |
366 | 95.2k | goto done; |
367 | 95.2k | } |
368 | 2.09k | if (c->sprites[palette_id].size != 256 * 2 * 3) { |
369 | 217 | av_log(avctx, AV_LOG_ERROR, |
370 | 217 | "Palette ID %d is wrong size (%d).\n", |
371 | 217 | palette_id, c->sprites[palette_id].size); |
372 | 217 | return AVERROR_INVALIDDATA; |
373 | 217 | } |
374 | 1.88k | c->last_pal_id = palette_id; |
375 | 1.88k | if (!palette_set) { |
376 | 1.67k | uint8_t *palette_data = c->sprites[palette_id].data; |
377 | 431k | for (int i = 0; i < 256; i++) { |
378 | | /* QuickTime-ish palette: 16-bit RGB components */ |
379 | 429k | unsigned r, g, b; |
380 | 429k | r = *palette_data; |
381 | 429k | g = *(palette_data + 2); |
382 | 429k | b = *(palette_data + 4); |
383 | 429k | c->pal[i] = (0xFFU << 24) | (r << 16) | (g << 8) | b; |
384 | 429k | palette_data += 6; |
385 | 429k | } |
386 | | /* first palette entry indicates transparency */ |
387 | 1.67k | c->pal[0] = 0; |
388 | 1.67k | } |
389 | 1.88k | } |
390 | | |
391 | 97.8k | done: |
392 | | /* discard outdated blocks */ |
393 | 117M | for (int i = 0; i < CDTOONS_MAX_SPRITES; i++) { |
394 | 117M | if (c->sprites[i].end_frame > frame_id) |
395 | 5.25k | continue; |
396 | 117M | c->sprites[i].active = 0; |
397 | 117M | } |
398 | | |
399 | 97.8k | memcpy(c->frame->data[1], c->pal, AVPALETTE_SIZE); |
400 | | |
401 | 97.8k | if ((ret = av_frame_ref(rframe, c->frame)) < 0) |
402 | 0 | return ret; |
403 | | |
404 | 97.8k | *got_frame = 1; |
405 | | |
406 | | /* always report that the buffer was completely consumed */ |
407 | 97.8k | return buf_size; |
408 | 97.8k | } |
409 | | |
410 | | static av_cold int cdtoons_decode_init(AVCodecContext *avctx) |
411 | 1.18k | { |
412 | 1.18k | CDToonsContext *c = avctx->priv_data; |
413 | | |
414 | 1.18k | avctx->pix_fmt = AV_PIX_FMT_PAL8; |
415 | 1.18k | c->last_pal_id = 0; |
416 | 1.18k | c->frame = av_frame_alloc(); |
417 | 1.18k | if (!c->frame) |
418 | 0 | return AVERROR(ENOMEM); |
419 | | |
420 | 1.18k | return 0; |
421 | 1.18k | } |
422 | | |
423 | | static void cdtoons_flush(AVCodecContext *avctx) |
424 | 67.6k | { |
425 | 67.6k | CDToonsContext *c = avctx->priv_data; |
426 | | |
427 | 67.6k | c->last_pal_id = 0; |
428 | 81.2M | for (int i = 0; i < CDTOONS_MAX_SPRITES; i++) |
429 | 81.1M | c->sprites[i].active = 0; |
430 | 67.6k | } |
431 | | |
432 | | static av_cold int cdtoons_decode_end(AVCodecContext *avctx) |
433 | 1.18k | { |
434 | 1.18k | CDToonsContext *c = avctx->priv_data; |
435 | | |
436 | 1.42M | for (int i = 0; i < CDTOONS_MAX_SPRITES; i++) { |
437 | 1.42M | av_freep(&c->sprites[i].data); |
438 | 1.42M | c->sprites[i].active = 0; |
439 | 1.42M | } |
440 | | |
441 | 1.18k | av_frame_free(&c->frame); |
442 | | |
443 | 1.18k | return 0; |
444 | 1.18k | } |
445 | | |
446 | | const FFCodec ff_cdtoons_decoder = { |
447 | | .p.name = "cdtoons", |
448 | | CODEC_LONG_NAME("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 | | }; |