diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index 4765e9c..f0b2aa3 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -290,6 +290,7 @@ OBJS-$(CONFIG_FLASHSV_ENCODER)         += flashsvenc.o
 OBJS-$(CONFIG_FLASHSV2_ENCODER)        += flashsv2enc.o
 OBJS-$(CONFIG_FLASHSV2_DECODER)        += flashsv.o
 OBJS-$(CONFIG_FLIC_DECODER)            += flicvideo.o
+OBJS-$(CONFIG_FMVC_DECODER)            += fmvc.o
 OBJS-$(CONFIG_FOURXM_DECODER)          += 4xm.o
 OBJS-$(CONFIG_FRAPS_DECODER)           += fraps.o
 OBJS-$(CONFIG_FRWU_DECODER)            += frwu.o
diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c
index f92b2b7..10fd61c 100644
--- a/libavcodec/allcodecs.c
+++ b/libavcodec/allcodecs.c
@@ -192,6 +192,7 @@ void avcodec_register_all(void)
     REGISTER_ENCDEC (FLASHSV2,          flashsv2);
     REGISTER_DECODER(FLIC,              flic);
     REGISTER_ENCDEC (FLV,               flv);
+    REGISTER_DECODER(FMVC,              fmvc);
     REGISTER_DECODER(FOURXM,            fourxm);
     REGISTER_DECODER(FRAPS,             fraps);
     REGISTER_DECODER(FRWU,              frwu);
diff --git a/libavcodec/avcodec.h b/libavcodec/avcodec.h
index 1e681e9..724e246 100644
--- a/libavcodec/avcodec.h
+++ b/libavcodec/avcodec.h
@@ -414,6 +414,7 @@ enum AVCodecID {
     AV_CODEC_ID_PSD,
     AV_CODEC_ID_PIXLET,
     AV_CODEC_ID_SPEEDHQ,
+    AV_CODEC_ID_FMVC,
 
     /* various PCM "codecs" */
     AV_CODEC_ID_FIRST_AUDIO = 0x10000,     ///< A dummy id pointing at the start of audio codecs
diff --git a/libavcodec/codec_desc.c b/libavcodec/codec_desc.c
index 09d3c48..cb3debd 100644
--- a/libavcodec/codec_desc.c
+++ b/libavcodec/codec_desc.c
@@ -1353,6 +1353,13 @@ static const AVCodecDescriptor codec_descriptors[] = {
         .long_name = NULL_IF_CONFIG_SMALL("Apple Pixlet"),
         .props     = AV_CODEC_PROP_INTRA_ONLY | AV_CODEC_PROP_LOSSY,
     },
+    {
+        .id        = AV_CODEC_ID_FMVC,
+        .type      = AVMEDIA_TYPE_VIDEO,
+        .name      = "fmvc",
+        .long_name = NULL_IF_CONFIG_SMALL("FM Screen Capture Codec"),
+        .props     = AV_CODEC_PROP_LOSSLESS,
+    },
 
     /* image codecs */
     {
diff --git a/libavcodec/fmvc.c b/libavcodec/fmvc.c
new file mode 100644
index 0000000..1c6d9ae
--- /dev/null
+++ b/libavcodec/fmvc.c
@@ -0,0 +1,629 @@
+/*
+ * FM Screen Capture Codec decoder
+ *
+ * Copyright (c) 2017 Paul B Mahol
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "avcodec.h"
+#include "bytestream.h"
+#include "internal.h"
+
+#define BLOCK_HEIGHT 112u
+#define BLOCK_WIDTH  84u
+
+typedef struct InterBlock {
+    int      w, h;
+    int      size;
+    int      xor;
+} InterBlock;
+
+typedef struct FMVCContext {
+    GetByteContext  gb;
+    PutByteContext  pb;
+    uint8_t        *buffer;
+    size_t          buffer_size;
+    uint8_t        *pbuffer;
+    size_t          pbuffer_size;
+    int             stride;
+    int             bpp;
+    int             yb, xb;
+    InterBlock     *blocks;
+    int             nb_blocks;
+} FMVCContext;
+
+static int decode_type2(GetByteContext *gb, PutByteContext *pb)
+{
+    unsigned repeat = 0, first = 1, opcode;
+    int i, len, pos;
+
+    while (bytestream2_get_bytes_left(gb) > 0) {
+        GetByteContext gbc;
+
+        while (bytestream2_get_bytes_left(gb) > 0) {
+            if (first) {
+                first = 0;
+                if (bytestream2_peek_byte(gb) > 17) {
+                    len = bytestream2_get_byte(gb) - 17;
+                    if (len < 4) {
+                        do {
+                            bytestream2_put_byte(pb, bytestream2_get_byte(gb));
+                            --len;
+                        } while (len);
+                        opcode = bytestream2_peek_byte(gb);
+                        continue;
+                    } else {
+                        do {
+                            bytestream2_put_byte(pb, bytestream2_get_byte(gb));
+                            --len;
+                        } while (len);
+                        opcode = bytestream2_peek_byte(gb);
+                        if (opcode < 0x10) {
+                            bytestream2_skip(gb, 1);
+                            pos = - (opcode >> 2) - 4 * bytestream2_get_byte(gb) - 2049;
+
+                            bytestream2_init(&gbc, pb->buffer_start, pb->buffer_end - pb->buffer_start);
+                            bytestream2_seek(&gbc, bytestream2_tell_p(pb) + pos, SEEK_SET);
+
+                            bytestream2_put_byte(pb, bytestream2_get_byte(&gbc));
+                            bytestream2_put_byte(pb, bytestream2_get_byte(&gbc));
+                            bytestream2_put_byte(pb, bytestream2_get_byte(&gbc));
+                            len = opcode & 3;
+                            if (!len) {
+                                repeat = 1;
+                            } else {
+                                do {
+                                    bytestream2_put_byte(pb, bytestream2_get_byte(gb));
+                                    --len;
+                                } while (len);
+                                opcode = bytestream2_peek_byte(gb);
+                            }
+                            continue;
+                        }
+                    }
+                    repeat = 0;
+                }
+                repeat = 1;
+            }
+            if (repeat) {
+                repeat = 0;
+                opcode = bytestream2_peek_byte(gb);
+                if (opcode < 0x10) {
+                    bytestream2_skip(gb, 1);
+                    if (!opcode) {
+                        if (!bytestream2_peek_byte(gb)) {
+                            do {
+                                opcode += 255;
+                                bytestream2_skip(gb, 1);
+                            } while (!bytestream2_peek_byte(gb));
+                        }
+                        opcode += bytestream2_get_byte(gb) + 15;
+                    }
+                    bytestream2_put_le32(pb, bytestream2_get_le32(gb));
+                    for (i = opcode - 1; i > 0; --i)
+                        bytestream2_put_byte(pb, bytestream2_get_byte(gb));
+                    opcode = bytestream2_peek_byte(gb);
+                    if (opcode < 0x10) {
+                        bytestream2_skip(gb, 1);
+                        pos = - (opcode >> 2) - 4 * bytestream2_get_byte(gb) - 2049;
+
+                        bytestream2_init(&gbc, pb->buffer_start, pb->buffer_end - pb->buffer_start);
+                        bytestream2_seek(&gbc, bytestream2_tell_p(pb) + pos, SEEK_SET);
+
+                        bytestream2_put_byte(pb, bytestream2_get_byte(&gbc));
+                        bytestream2_put_byte(pb, bytestream2_get_byte(&gbc));
+                        bytestream2_put_byte(pb, bytestream2_get_byte(&gbc));
+                        len = opcode & 3;
+                        if (!len) {
+                            repeat = 1;
+                        } else {
+                            do {
+                                bytestream2_put_byte(pb, bytestream2_get_byte(gb));
+                                --len;
+                            } while (len);
+                            opcode = bytestream2_peek_byte(gb);
+                        }
+                        continue;
+                    }
+                }
+            }
+
+            if (opcode >= 0x40) {
+                bytestream2_skip(gb, 1);
+                pos = - ((opcode >> 2) & 7) - 1 - 8 * bytestream2_get_byte(gb);
+                len = (opcode >> 5) - 1;
+
+                bytestream2_init(&gbc, pb->buffer_start, pb->buffer_end - pb->buffer_start);
+                bytestream2_seek(&gbc, bytestream2_tell_p(pb) + pos, SEEK_SET);
+
+                bytestream2_put_byte(pb, bytestream2_get_byte(&gbc));
+                bytestream2_put_byte(pb, bytestream2_get_byte(&gbc));
+                do {
+                    bytestream2_put_byte(pb, bytestream2_get_byte(&gbc));
+                    --len;
+                } while (len);
+
+                len = opcode & 3;
+
+                if (!len) {
+                    repeat = 1;
+                } else {
+                    do {
+                        bytestream2_put_byte(pb, bytestream2_get_byte(gb));
+                        --len;
+                    } while (len);
+                    opcode = bytestream2_peek_byte(gb);
+                }
+                continue;
+            } else if (opcode < 0x20) {
+                break;
+            }
+            len = opcode & 0x1F;
+            bytestream2_skip(gb, 1);
+            if (!len) {
+                if (!bytestream2_peek_byte(gb)) {
+                    do {
+                        len += 255;
+                        bytestream2_skip(gb, 1);
+                    } while (!bytestream2_peek_byte(gb));
+                }
+                len += bytestream2_get_byte(gb) + 31;
+            }
+            i = bytestream2_get_le16(gb);
+            pos = - (i >> 2) - 1;
+
+            bytestream2_init(&gbc, pb->buffer_start, pb->buffer_end - pb->buffer_start);
+            bytestream2_seek(&gbc, bytestream2_tell_p(pb) + pos, SEEK_SET);
+
+            if (len < 6 || bytestream2_tell_p(pb) - bytestream2_tell(&gbc) < 4) {
+                bytestream2_put_byte(pb, bytestream2_get_byte(&gbc));
+                bytestream2_put_byte(pb, bytestream2_get_byte(&gbc));
+                do {
+                    bytestream2_put_byte(pb, bytestream2_get_byte(&gbc));
+                    --len;
+                } while (len);
+            } else {
+                bytestream2_put_le32(pb, bytestream2_get_le32(&gbc));
+                for (len = len - 2; len; --len)
+                    bytestream2_put_byte(pb, bytestream2_get_byte(&gbc));
+            }
+            len = i & 3;
+            if (!len) {
+                repeat = 1;
+            } else {
+                do {
+                    bytestream2_put_byte(pb, bytestream2_get_byte(gb));
+                    --len;
+                } while (len);
+                opcode = bytestream2_peek_byte(gb);
+            }
+        }
+        bytestream2_skip(gb, 1);
+        if (opcode < 0x10) {
+            pos = -(opcode >> 2) - 1 - 4 * bytestream2_get_byte(gb);
+
+            bytestream2_init(&gbc, pb->buffer_start, pb->buffer_end - pb->buffer_start);
+            bytestream2_seek(&gbc, bytestream2_tell_p(pb) + pos, SEEK_SET);
+
+            bytestream2_put_byte(pb, bytestream2_get_byte(&gbc));
+            bytestream2_put_byte(pb, bytestream2_get_byte(&gbc));
+            len = opcode & 3;
+            if (!len) {
+                repeat = 1;
+            } else {
+                do {
+                    bytestream2_put_byte(pb, bytestream2_get_byte(gb));
+                    --len;
+                } while (len);
+                opcode = bytestream2_peek_byte(gb);
+            }
+            continue;
+        }
+        len = opcode & 7;
+        if (!len) {
+            if (!bytestream2_peek_byte(gb)) {
+                do {
+                    bytestream2_skip(gb, 1);
+                    len += 255;
+                } while (!bytestream2_peek_byte(gb));
+            }
+            len += bytestream2_get_byte(gb) + 7;
+        }
+        i = bytestream2_get_le16(gb);
+        pos = bytestream2_tell_p(pb) - 2048 * (opcode & 8);
+        pos = pos - (i >> 2);
+        if (pos != bytestream2_tell_p(pb)) {
+            pos = pos - 0x4000;
+
+            bytestream2_init(&gbc, pb->buffer_start, pb->buffer_end - pb->buffer_start);
+            bytestream2_seek(&gbc, pos, SEEK_SET);
+
+            if (len < 6 || bytestream2_tell_p(pb) - bytestream2_tell(&gbc) < 4) {
+                bytestream2_put_byte(pb, bytestream2_get_byte(&gbc));
+                bytestream2_put_byte(pb, bytestream2_get_byte(&gbc));
+                do {
+                    bytestream2_put_byte(pb, bytestream2_get_byte(&gbc));
+                    --len;
+                } while (len);
+            } else {
+                bytestream2_put_le32(pb, bytestream2_get_le32(&gbc));
+                for (len = len - 2; len; --len)
+                    bytestream2_put_byte(pb, bytestream2_get_byte(&gbc));
+            }
+
+            len = i & 3;
+            if (!len) {
+                repeat = 1;
+            } else {
+                do {
+                    bytestream2_put_byte(pb, bytestream2_get_byte(gb));
+                    --len;
+                } while (len);
+                opcode = bytestream2_peek_byte(gb);
+            }
+            continue;
+        }
+        break;
+    }
+
+    return 0;
+}
+
+static int decode_type1(GetByteContext *gb, PutByteContext *pb)
+{
+    unsigned opcode, len;
+    int high = 0;
+    int i, pos;
+
+    while (bytestream2_get_bytes_left(gb) > 0) {
+        GetByteContext gbc;
+
+        while (bytestream2_get_bytes_left(gb) > 0) {
+            while (bytestream2_get_bytes_left(gb) > 0) {
+                opcode = bytestream2_get_byte(gb);
+                high = opcode >= 0x20;
+                if (high)
+                    break;
+                if (opcode)
+                    break;
+                opcode = bytestream2_get_byte(gb);
+                if (opcode < 0xF8) {
+                    opcode = opcode + 32;
+                    break;
+                }
+                i = opcode - 0xF8;
+                if (i) {
+                    len = 256;
+                    do {
+                        len *= 2;
+                        --i;
+                    } while (i);
+                } else {
+                    len = 280;
+                }
+                do {
+                    bytestream2_put_le32(pb, bytestream2_get_le32(gb));
+                    bytestream2_put_le32(pb, bytestream2_get_le32(gb));
+                    len -= 8;
+                } while (len && bytestream2_get_bytes_left(gb) > 0);
+            }
+
+            if (!high) {
+                do {
+                    bytestream2_put_byte(pb, bytestream2_get_byte(gb));
+                    --opcode;
+                } while (opcode && bytestream2_get_bytes_left(gb) > 0);
+
+                while (bytestream2_get_bytes_left(gb) > 0) {
+                    GetByteContext gbc;
+
+                    opcode = bytestream2_get_byte(gb);
+                    if (opcode >= 0x20)
+                        break;
+                    bytestream2_init(&gbc, pb->buffer_start, pb->buffer_end - pb->buffer_start);
+
+                    pos = -(opcode | 32 * bytestream2_get_byte(gb)) - 1;
+                    bytestream2_seek(&gbc, bytestream2_tell_p(pb) + pos, SEEK_SET);
+                    bytestream2_put_byte(pb, bytestream2_get_byte(&gbc));
+                    bytestream2_put_byte(pb, bytestream2_get_byte(&gbc));
+                    bytestream2_put_byte(pb, bytestream2_get_byte(&gbc));
+                    bytestream2_put_byte(pb, bytestream2_get_byte(gb));
+                }
+            }
+            high = 0;
+            if (opcode < 0x40)
+                break;
+            bytestream2_init(&gbc, pb->buffer_start, pb->buffer_end - pb->buffer_start);
+            pos = (-((opcode & 0x1F) | 32 * bytestream2_get_byte(gb)) - 1);
+            bytestream2_seek(&gbc, bytestream2_tell_p(pb) + pos, SEEK_SET);
+            bytestream2_put_byte(pb, bytestream2_get_byte(&gbc));
+            bytestream2_put_byte(pb, bytestream2_get_byte(&gbc));
+            len = (opcode >> 5) - 1;
+            do {
+                bytestream2_put_byte(pb, bytestream2_get_byte(&gbc));
+                --len;
+            } while (len && bytestream2_get_bytes_left(&gbc) > 0);
+        }
+        len = opcode & 0x1F;
+        if (!len) {
+            if (!bytestream2_peek_byte(gb)) {
+                do {
+                    bytestream2_skip(gb, 1);
+                    len += 255;
+                } while (!bytestream2_peek_byte(gb) && bytestream2_get_bytes_left(gb) > 0);
+            }
+            len += bytestream2_get_byte(gb) + 31;
+        }
+        pos = -bytestream2_get_byte(gb);
+        bytestream2_init(&gbc, pb->buffer_start, pb->buffer_end - pb->buffer_start);
+        bytestream2_seek(&gbc, bytestream2_tell_p(pb) + pos - (bytestream2_get_byte(gb) << 8), SEEK_SET);
+        if (bytestream2_tell_p(pb) == bytestream2_tell(&gbc))
+            break;
+        if (len < 5 || bytestream2_tell_p(pb) - bytestream2_tell(&gbc) < 4) {
+            bytestream2_put_byte(pb, bytestream2_get_byte(&gbc));
+            bytestream2_put_byte(pb, bytestream2_get_byte(&gbc));
+            bytestream2_put_byte(pb, bytestream2_get_byte(&gbc));
+            do {
+                bytestream2_put_byte(pb, bytestream2_get_byte(&gbc));
+                --len;
+            } while (len && bytestream2_get_bytes_left(&gbc) > 0);
+        } else {
+            bytestream2_put_le32(pb, bytestream2_get_le32(&gbc));
+            len--;
+            do {
+                bytestream2_put_byte(pb, bytestream2_get_byte(&gbc));
+                len--;
+            } while (len && bytestream2_get_bytes_left(&gbc) > 0);
+        }
+    }
+
+    return 0;
+}
+
+static int decode_frame(AVCodecContext *avctx,
+                        void *data, int *got_frame,
+                        AVPacket *avpkt)
+{
+    FMVCContext *s = avctx->priv_data;
+    GetByteContext *gb = &s->gb;
+    PutByteContext *pb = &s->pb;
+    AVFrame *frame = data;
+    int ret, y, x;
+
+    if ((ret = ff_get_buffer(avctx, frame, 0)) < 0)
+        return ret;
+
+    bytestream2_init(gb, avpkt->data, avpkt->size);
+    bytestream2_skip(gb, 2);
+
+    frame->key_frame = !!bytestream2_get_le16(gb);
+    frame->pict_type = frame->key_frame ? AV_PICTURE_TYPE_I : AV_PICTURE_TYPE_P;
+
+    if (frame->key_frame) {
+        uint8_t *src, *dst;
+        int type, size;
+
+        type = bytestream2_get_le16(gb);
+        size = bytestream2_get_le16(gb);
+        if (size > bytestream2_get_bytes_left(gb))
+            return AVERROR_INVALIDDATA;
+
+        bytestream2_init_writer(pb, s->buffer, s->buffer_size);
+        if (type == 1) {
+            decode_type1(gb, pb);
+        } else if (type == 2){
+            decode_type2(gb, pb);
+        } else {
+            avpriv_report_missing_feature(avctx, "compression %d", type);
+            return AVERROR_PATCHWELCOME;
+        }
+
+        src = s->buffer;
+        dst = frame->data[0] + (avctx->height - 1) * frame->linesize[0];
+        for (y = 0; y < avctx->height; y++) {
+            memcpy(dst, src, avctx->width * s->bpp);
+            dst -= frame->linesize[0];
+            src += avctx->width * s->bpp;
+        }
+    } else {
+        int block, nb_blocks, type, k, l;
+        uint32_t *src, *dst;
+        uint8_t *ssrc, *ddst;
+
+        for (block = 0; block < s->nb_blocks; block++)
+            s->blocks[block].xor = 0;
+
+        nb_blocks = bytestream2_get_le16(gb);
+        if (nb_blocks > s->nb_blocks)
+            return AVERROR_INVALIDDATA;
+
+        bytestream2_init_writer(pb, s->pbuffer, s->pbuffer_size);
+
+        type = bytestream2_get_le16(gb);
+        for (block = 0; block < nb_blocks; block++) {
+            int size, offset, start = 0;
+
+            offset = bytestream2_get_le16(gb);
+            if (offset > s->nb_blocks)
+                return AVERROR_INVALIDDATA;
+
+            size = bytestream2_get_le16(gb);
+            if (size > bytestream2_get_bytes_left(gb))
+                return AVERROR_INVALIDDATA;
+
+            start = bytestream2_tell_p(pb);
+            if (type == 1) {
+                decode_type1(gb, pb);
+            } else if (type == 2){
+                decode_type2(gb, pb);
+            } else {
+                avpriv_report_missing_feature(avctx, "compression %d", type);
+                return AVERROR_PATCHWELCOME;
+            }
+
+            if (s->blocks[offset].size * 4 != bytestream2_tell_p(pb) - start)
+                return AVERROR_INVALIDDATA;
+
+            s->blocks[offset].xor = 1;
+        }
+
+        src = (uint32_t *)s->pbuffer;
+        dst = (uint32_t *)s->buffer;
+
+        for (block = 0, y = 0; y < s->yb; y++) {
+            int block_h = s->blocks[block].h;
+            uint32_t *output3 = dst;
+
+            for (x = 0; x < s->xb; x++) {
+                int block_w = s->blocks[block].w;
+                uint32_t *output4 = dst;
+
+                block_h = s->blocks[block].h;
+                if (s->blocks[block].xor) {
+                    for (k = 0; k < block_h; k++) {
+                        uint32_t *output5 = dst;
+                        for (l = 0; l < block_w; l++) {
+                            *dst++ ^= *src++;
+                        }
+                        dst = &output5[s->stride];
+                    }
+                }
+                dst = &output4[block_w];
+                ++block;
+            }
+            dst = &output3[block_h * s->stride];
+        }
+
+        ssrc = s->buffer;
+        ddst = frame->data[0] + (avctx->height - 1) * frame->linesize[0];
+        for (y = 0; y < avctx->height; y++) {
+            memcpy(ddst, ssrc, avctx->width * s->bpp);
+            ddst -= frame->linesize[0];
+            ssrc += avctx->width * s->bpp;
+        }
+    }
+
+    *got_frame = 1;
+
+    return avpkt->size;
+}
+
+static av_cold int decode_init(AVCodecContext *avctx)
+{
+    FMVCContext *s = avctx->priv_data;
+    int i, j, m, block = 0, h = BLOCK_HEIGHT, w = BLOCK_WIDTH;
+
+    switch (avctx->bits_per_coded_sample) {
+    case 16: avctx->pix_fmt = AV_PIX_FMT_RGB555; break;
+    case 24: avctx->pix_fmt = AV_PIX_FMT_BGR24;  break;
+    case 32: avctx->pix_fmt = AV_PIX_FMT_BGRA;   break;
+    default:
+        av_log(avctx, AV_LOG_ERROR, "Unsupported bitdepth %i\n", avctx->bits_per_coded_sample);
+        return AVERROR_INVALIDDATA;
+    }
+
+    s->stride = (avctx->width * avctx->bits_per_coded_sample + 31) / 32;
+    s->xb = s->stride / BLOCK_WIDTH;
+    m = s->stride % BLOCK_WIDTH;
+    if (m) {
+        if (m < 37) {
+            w = m + BLOCK_WIDTH;
+        } else {
+            w = m;
+            s->xb++;
+        }
+    }
+
+    s->yb = avctx->height / BLOCK_HEIGHT;
+    m = avctx->height % BLOCK_HEIGHT;
+    if (m) {
+        if (m < 49) {
+            h = m + BLOCK_HEIGHT;
+        } else {
+            h = m;
+            s->yb++;
+        }
+    }
+
+    s->nb_blocks = s->xb * s->yb;
+    s->blocks = av_calloc(s->nb_blocks, sizeof(*s->blocks));
+    if (!s->blocks)
+        return AVERROR(ENOMEM);
+
+    for (i = 0; i < s->yb; i++) {
+        for (j = 0; j < s->xb; j++) {
+            if (i != (s->yb - 1) || j != (s->xb - 1)) {
+                if (i == s->yb - 1) {
+                    s->blocks[block].w = BLOCK_WIDTH;
+                    s->blocks[block].h = h;
+                    s->blocks[block].size = BLOCK_WIDTH * h;
+                } else if (j == s->xb - 1) {
+                    s->blocks[block].w = w;
+                    s->blocks[block].h = BLOCK_HEIGHT;
+                    s->blocks[block].size = BLOCK_HEIGHT * w;
+                } else {
+                    s->blocks[block].w = BLOCK_WIDTH;
+                    s->blocks[block].h = BLOCK_HEIGHT;
+                    s->blocks[block].size = BLOCK_WIDTH * BLOCK_HEIGHT;
+                }
+            } else {
+                s->blocks[block].w = w;
+                s->blocks[block].h = h;
+                s->blocks[block].size = w * h;
+            }
+            block++;
+        }
+    }
+
+    s->bpp = avctx->bits_per_coded_sample >> 3;
+    s->buffer_size = avctx->width * avctx->height * 4;
+    s->pbuffer_size = avctx->width * avctx->height * 4;
+    s->buffer = av_malloc(s->buffer_size);
+    s->pbuffer = av_malloc(s->pbuffer_size);
+    if (!s->buffer || !s->pbuffer)
+        return AVERROR(ENOMEM);
+
+    return 0;
+}
+
+static av_cold int decode_end(AVCodecContext *avctx)
+{
+    FMVCContext *s = avctx->priv_data;
+
+    av_freep(&s->buffer);
+    av_freep(&s->pbuffer);
+    av_freep(&s->blocks);
+
+    return 0;
+}
+
+AVCodec ff_fmvc_decoder = {
+    .name             = "fmvc",
+    .long_name        = NULL_IF_CONFIG_SMALL("FM Screen Capture Codec"),
+    .type             = AVMEDIA_TYPE_VIDEO,
+    .id               = AV_CODEC_ID_FMVC,
+    .priv_data_size   = sizeof(FMVCContext),
+    .init             = decode_init,
+    .close            = decode_end,
+    .decode           = decode_frame,
+    .capabilities     = AV_CODEC_CAP_DR1,
+};
diff --git a/libavformat/riff.c b/libavformat/riff.c
index d44b908..7be50c3 100644
--- a/libavformat/riff.c
+++ b/libavformat/riff.c
@@ -448,6 +448,7 @@ const AVCodecTag ff_codec_bmp_tags[] = {
     { AV_CODEC_ID_SPEEDHQ,      MKTAG('S', 'H', 'Q', '5') },
     { AV_CODEC_ID_SPEEDHQ,      MKTAG('S', 'H', 'Q', '7') },
     { AV_CODEC_ID_SPEEDHQ,      MKTAG('S', 'H', 'Q', '9') },
+    { AV_CODEC_ID_FMVC,         MKTAG('F', 'M', 'V', 'C') },
 
     { AV_CODEC_ID_NONE,         0 }
 };
