Coverage Report

Created: 2025-08-28 07:12

/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
};