Coverage Report

Created: 2026-02-14 06:59

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/ffmpeg/libavcodec/mmvideo.c
Line
Count
Source
1
/*
2
 * American Laser Games MM Video Decoder
3
 * Copyright (c) 2006,2008 Peter Ross
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
 * American Laser Games MM Video Decoder
25
 * by Peter Ross (pross@xvid.org)
26
 *
27
 * The MM format was used by IBM-PC ports of ALG's "arcade shooter" games,
28
 * including Mad Dog McCree and Crime Patrol.
29
 *
30
 * Technical details here:
31
 *  http://wiki.multimedia.cx/index.php?title=American_Laser_Games_MM
32
 */
33
34
#include "libavutil/intreadwrite.h"
35
#include "avcodec.h"
36
#include "bytestream.h"
37
#include "codec_internal.h"
38
#include "decode.h"
39
40
414k
#define MM_PREAMBLE_SIZE    6
41
42
745
#define MM_TYPE_RAW         0x2
43
7.93k
#define MM_TYPE_INTER       0x5
44
1.88k
#define MM_TYPE_INTRA       0x8
45
1.22k
#define MM_TYPE_INTRA_HH    0xc
46
668
#define MM_TYPE_INTER_HH    0xd
47
1.31k
#define MM_TYPE_INTRA_HHV   0xe
48
966
#define MM_TYPE_INTER_HHV   0xf
49
118k
#define MM_TYPE_PALETTE     0x31
50
51
typedef struct MmContext {
52
    AVCodecContext *avctx;
53
    AVFrame *frame;
54
    unsigned int palette[AVPALETTE_COUNT];
55
    GetByteContext gb;
56
} MmContext;
57
58
static av_cold int mm_decode_init(AVCodecContext *avctx)
59
732
{
60
732
    MmContext *s = avctx->priv_data;
61
62
732
    s->avctx = avctx;
63
64
732
    avctx->pix_fmt = AV_PIX_FMT_PAL8;
65
66
732
    if (!avctx->width || !avctx->height ||
67
594
        (avctx->width & 1) || (avctx->height & 1)) {
68
149
        av_log(avctx, AV_LOG_ERROR, "Invalid video dimensions: %dx%d\n",
69
149
               avctx->width, avctx->height);
70
149
        return AVERROR(EINVAL);
71
149
    }
72
73
583
    s->frame = av_frame_alloc();
74
583
    if (!s->frame)
75
0
        return AVERROR(ENOMEM);
76
77
583
    return 0;
78
583
}
79
80
static int mm_decode_raw(MmContext * s)
81
745
{
82
745
    if (bytestream2_get_bytes_left(&s->gb) < s->avctx->width * s->avctx->height)
83
266
        return AVERROR_INVALIDDATA;
84
274k
    for (int y = 0; y < s->avctx->height; y++)
85
274k
        bytestream2_get_buffer(&s->gb, s->frame->data[0] + y*s->frame->linesize[0], s->avctx->width);
86
479
    return 0;
87
745
}
88
89
static void mm_decode_pal(MmContext *s)
90
118k
{
91
118k
    int start = bytestream2_get_le16(&s->gb);
92
118k
    int count = bytestream2_get_le16(&s->gb);
93
68.5M
    for (int i = 0; i < count; i++)
94
68.4M
        s->palette[(start+i)&0xFF] = 0xFFU << 24 | (bytestream2_get_be24(&s->gb) << 2);
95
118k
}
96
97
/**
98
 * @param half_horiz Half horizontal resolution (0 or 1)
99
 * @param half_vert Half vertical resolution (0 or 1)
100
 */
101
static int mm_decode_intra(MmContext * s, int half_horiz, int half_vert)
102
4.42k
{
103
4.42k
    int x = 0, y = 0;
104
105
431k
    while (bytestream2_get_bytes_left(&s->gb) > 0) {
106
428k
        int run_length, color;
107
108
428k
        if (y >= s->avctx->height)
109
758
            return 0;
110
111
427k
        color = bytestream2_get_byte(&s->gb);
112
427k
        if (color & 0x80) {
113
241k
            run_length = 1;
114
241k
        }else{
115
186k
            run_length = (color & 0x7f) + 2;
116
186k
            color = bytestream2_get_byte(&s->gb);
117
186k
        }
118
119
427k
        if (half_horiz)
120
139k
            run_length *=2;
121
122
427k
        if (run_length > s->avctx->width - x)
123
1.34k
            return AVERROR_INVALIDDATA;
124
125
426k
        if (color) {
126
270k
            memset(s->frame->data[0] + y*s->frame->linesize[0] + x, color, run_length);
127
270k
            if (half_vert && y + half_vert < s->avctx->height)
128
37.7k
                memset(s->frame->data[0] + (y+1)*s->frame->linesize[0] + x, color, run_length);
129
270k
        }
130
426k
        x+= run_length;
131
132
426k
        if (x >= s->avctx->width) {
133
2.89k
            x=0;
134
2.89k
            y += 1 + half_vert;
135
2.89k
        }
136
426k
    }
137
138
2.31k
    return 0;
139
4.42k
}
140
141
/**
142
 * @param half_horiz Half horizontal resolution (0 or 1)
143
 * @param half_vert Half vertical resolution (0 or 1)
144
 */
145
static int mm_decode_inter(MmContext * s, int half_horiz, int half_vert)
146
9.57k
{
147
9.57k
    int data_off = bytestream2_get_le16(&s->gb);
148
9.57k
    int y = 0;
149
9.57k
    GetByteContext data_ptr;
150
151
9.57k
    if (bytestream2_get_bytes_left(&s->gb) < data_off)
152
1.06k
        return AVERROR_INVALIDDATA;
153
154
8.50k
    bytestream2_init(&data_ptr, s->gb.buffer + data_off, bytestream2_get_bytes_left(&s->gb) - data_off);
155
36.4k
    while (s->gb.buffer < data_ptr.buffer_start) {
156
28.2k
        int i, j;
157
28.2k
        int length = bytestream2_get_byte(&s->gb);
158
28.2k
        int x = bytestream2_get_byte(&s->gb) + ((length & 0x80) << 1);
159
28.2k
        length &= 0x7F;
160
161
28.2k
        if (length==0) {
162
26.3k
            y += x;
163
26.3k
            continue;
164
26.3k
        }
165
166
1.84k
        if (y + half_vert >= s->avctx->height)
167
307
            return 0;
168
169
59.7k
        for(i=0; i<length; i++) {
170
58.1k
            int replace_array = bytestream2_get_byte(&s->gb);
171
384k
            for(j=0; j<8; j++) {
172
343k
                int replace = (replace_array >> (7-j)) & 1;
173
343k
                if (x + half_horiz >= s->avctx->width)
174
17.4k
                    break;
175
326k
                if (replace) {
176
39.4k
                    int color = bytestream2_get_byte(&data_ptr);
177
39.4k
                    s->frame->data[0][y*s->frame->linesize[0] + x] = color;
178
39.4k
                    if (half_horiz)
179
13.8k
                        s->frame->data[0][y*s->frame->linesize[0] + x + 1] = color;
180
39.4k
                    if (half_vert) {
181
9.11k
                        s->frame->data[0][(y+1)*s->frame->linesize[0] + x] = color;
182
9.11k
                        if (half_horiz)
183
9.11k
                            s->frame->data[0][(y+1)*s->frame->linesize[0] + x + 1] = color;
184
9.11k
                    }
185
39.4k
                }
186
326k
                x += 1 + half_horiz;
187
326k
            }
188
58.1k
        }
189
190
1.54k
        y += 1 + half_vert;
191
1.54k
    }
192
193
8.19k
    return 0;
194
8.50k
}
195
196
static int mm_decode_frame(AVCodecContext *avctx, AVFrame *rframe,
197
                           int *got_frame, AVPacket *avpkt)
198
140k
{
199
140k
    const uint8_t *buf = avpkt->data;
200
140k
    int buf_size = avpkt->size;
201
140k
    MmContext *s = avctx->priv_data;
202
140k
    int type, res;
203
204
140k
    if (buf_size < MM_PREAMBLE_SIZE)
205
3.77k
        return AVERROR_INVALIDDATA;
206
137k
    type = AV_RL16(&buf[0]);
207
137k
    buf += MM_PREAMBLE_SIZE;
208
137k
    buf_size -= MM_PREAMBLE_SIZE;
209
137k
    bytestream2_init(&s->gb, buf, buf_size);
210
211
137k
    if ((res = ff_reget_buffer(avctx, s->frame, 0)) < 0)
212
60
        return res;
213
214
137k
    switch(type) {
215
745
    case MM_TYPE_RAW       : res = mm_decode_raw(s); break;
216
118k
    case MM_TYPE_PALETTE   : mm_decode_pal(s); return avpkt->size;
217
1.88k
    case MM_TYPE_INTRA     : res = mm_decode_intra(s, 0, 0); break;
218
1.22k
    case MM_TYPE_INTRA_HH  : res = mm_decode_intra(s, 1, 0); break;
219
1.31k
    case MM_TYPE_INTRA_HHV : res = mm_decode_intra(s, 1, 1); break;
220
7.93k
    case MM_TYPE_INTER     : res = mm_decode_inter(s, 0, 0); break;
221
668
    case MM_TYPE_INTER_HH  : res = mm_decode_inter(s, 1, 0); break;
222
966
    case MM_TYPE_INTER_HHV : res = mm_decode_inter(s, 1, 1); break;
223
3.83k
    default:
224
3.83k
        res = AVERROR_INVALIDDATA;
225
3.83k
        break;
226
137k
    }
227
18.5k
    if (res < 0)
228
6.51k
        return res;
229
230
12.0k
    memcpy(s->frame->data[1], s->palette, AVPALETTE_SIZE);
231
232
12.0k
    if ((res = av_frame_ref(rframe, s->frame)) < 0)
233
0
        return res;
234
235
12.0k
    *got_frame      = 1;
236
237
12.0k
    return avpkt->size;
238
12.0k
}
239
240
static av_cold int mm_decode_end(AVCodecContext *avctx)
241
583
{
242
583
    MmContext *s = avctx->priv_data;
243
244
583
    av_frame_free(&s->frame);
245
246
583
    return 0;
247
583
}
248
249
const FFCodec ff_mmvideo_decoder = {
250
    .p.name         = "mmvideo",
251
    CODEC_LONG_NAME("American Laser Games MM Video"),
252
    .p.type         = AVMEDIA_TYPE_VIDEO,
253
    .p.id           = AV_CODEC_ID_MMVIDEO,
254
    .priv_data_size = sizeof(MmContext),
255
    .init           = mm_decode_init,
256
    .close          = mm_decode_end,
257
    FF_CODEC_DECODE_CB(mm_decode_frame),
258
    .p.capabilities = AV_CODEC_CAP_DR1,
259
};