Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
 help / color / mirror / Atom feed
* [FFmpeg-devel] [PATCH v2 00/13] avcodec/sanm: various improvements
@ 2025-03-15  9:32 Manuel Lauss
  2025-03-15  9:32 ` [FFmpeg-devel] [PATCH v2 01/13] avcodec/sanm: disable left/top for fullscreen codecs Manuel Lauss
                   ` (12 more replies)
  0 siblings, 13 replies; 15+ messages in thread
From: Manuel Lauss @ 2025-03-15  9:32 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: Manuel Lauss

This patchset improves especially support for SMUSHv1 (Rebel Assault 1),
adds missing codecs and functions and improves reliability of SMUSHv1/v2.

Changes in v2:
- reordered first 3 patches as some regressed things without the following
  patch applied.
- fixed codec4 left/top being swapped, resulting in weird visuals in L5PLAY.ANM
- additional handling for unaligned objects in file (see #4)
- reimplement ANIMv1 store/fetch handling (see #11)
- added codec45 to ignore (see #12 for an explanation)
- add myself as sanm.c/smush.c Maintainer, as requested by Michael (#13)

There are still a few palette issues in RA1: when the first FRME does not
contain any video data, the palette seems to be all-black until the first
NPAL (=new palette) chunk is encountered, although every ANM and SAN file
have a full palette in their header.

Manuel Lauss (13):
  avcodec/sanm: disable left/top for fullscreen codecs
  avcodec/sanm: FOBJ left/top are signed values
  avcodec/sanm: better frame size detection for old codecs
  avcodec/sanm: better ANIMv1 engine support
  avcodec/sanm: fix codec3
  avcodec/sanm: codec2 decoder
  avcodec/sanm: codec23 decoder
  avcodec/sanm: codec21 decoder
  avcodec/sanm: codec4/5/33/34 decoder
  avcodec/sanm: codec37: reimplement comp4
  avcodec/sanm: implement STOR/FTCH for ANIMv1
  avcodec/sanm: ignore codec45
  MAINTAINERS: add Manuel Lauss as sanm.c/smush.c Maintainer

 MAINTAINERS       |   2 +
 libavcodec/sanm.c | 687 ++++++++++++++++++++++++++++++++++++++++------
 2 files changed, 598 insertions(+), 91 deletions(-)

-- 
2.48.1

_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".

^ permalink raw reply	[flat|nested] 15+ messages in thread

* [FFmpeg-devel] [PATCH v2 01/13] avcodec/sanm: disable left/top for fullscreen codecs
  2025-03-15  9:32 [FFmpeg-devel] [PATCH v2 00/13] avcodec/sanm: various improvements Manuel Lauss
@ 2025-03-15  9:32 ` Manuel Lauss
  2025-03-15  9:32 ` [FFmpeg-devel] [PATCH v2 02/13] avcodec/sanm: FOBJ left/top are signed values Manuel Lauss
                   ` (11 subsequent siblings)
  12 siblings, 0 replies; 15+ messages in thread
From: Manuel Lauss @ 2025-03-15  9:32 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: Manuel Lauss

The block-based codecs 37/47/48 work on the full frame, and there's no
existing LucasArts game video that uses left/top offsets for these,
as it doesn't make sense. Ignore the left/top parameters for these codecs.

Signed-off-by: Manuel Lauss <manuel.lauss@gmail.com>
---
 libavcodec/sanm.c | 32 +++++++++++++++-----------------
 1 file changed, 15 insertions(+), 17 deletions(-)

diff --git a/libavcodec/sanm.c b/libavcodec/sanm.c
index a4f0a28c7c..49ac9bebfe 100644
--- a/libavcodec/sanm.c
+++ b/libavcodec/sanm.c
@@ -621,8 +621,7 @@ static inline void codec37_mv(uint8_t *dst, const uint8_t *src,
     }
 }
 
-static int old_codec37(SANMVideoContext *ctx, int top,
-                       int left, int width, int height)
+static int old_codec37(SANMVideoContext *ctx, int width, int height)
 {
     int i, j, k, l, t, run, len, code, skip, mx, my;
     ptrdiff_t stride = ctx->pitch;
@@ -638,8 +637,8 @@ static int old_codec37(SANMVideoContext *ctx, int top,
     flags = bytestream2_get_byte(&ctx->gb);
     bytestream2_skip(&ctx->gb, 3);
 
-    if (decoded_size > ctx->height * stride - left - top * stride) {
-        decoded_size = ctx->height * stride - left - top * stride;
+    if (decoded_size > ctx->height * stride) {
+        decoded_size = ctx->height * stride;
         av_log(ctx->avctx, AV_LOG_WARNING, "Decoded size is too large.\n");
     }
 
@@ -649,8 +648,8 @@ static int old_codec37(SANMVideoContext *ctx, int top,
         FFSWAP(uint16_t*, ctx->frm1, ctx->frm2);
     }
 
-    dst  = ((uint8_t*)ctx->frm1) + left + top * stride;
-    prev = ((uint8_t*)ctx->frm2) + left + top * stride;
+    dst  = ((uint8_t*)ctx->frm1);
+    prev = ((uint8_t*)ctx->frm2);
 
     if (mvoff > 2) {
         av_log(ctx->avctx, AV_LOG_ERROR, "Invalid motion base value %d.\n", mvoff);
@@ -925,15 +924,14 @@ static void codec47_comp1(SANMVideoContext *ctx, uint8_t *dst_in, int width,
     }
 }
 
-static int old_codec47(SANMVideoContext *ctx, int top,
-                       int left, int width, int height)
+static int old_codec47(SANMVideoContext *ctx, int width, int height)
 {
     uint32_t decoded_size;
     int i, j;
     ptrdiff_t stride = ctx->pitch;
-    uint8_t *dst   = (uint8_t *)ctx->frm0 + left + top * stride;
-    uint8_t *prev1 = (uint8_t *)ctx->frm1 + left + top * stride;
-    uint8_t *prev2 = (uint8_t *)ctx->frm2 + left + top * stride;
+    uint8_t *dst   = (uint8_t *)ctx->frm0;
+    uint8_t *prev1 = (uint8_t *)ctx->frm1;
+    uint8_t *prev2 = (uint8_t *)ctx->frm2;
     uint8_t auxcol[2];
     int tbl_pos = bytestream2_tell(&ctx->gb);
     int seq     = bytestream2_get_le16(&ctx->gb);
@@ -947,8 +945,8 @@ static int old_codec47(SANMVideoContext *ctx, int top,
     decoded_size = bytestream2_get_le32(&ctx->gb);
     bytestream2_skip(&ctx->gb, 8);
 
-    if (decoded_size > ctx->height * stride - left - top * stride) {
-        decoded_size = ctx->height * stride - left - top * stride;
+    if (decoded_size > ctx->height * stride) {
+        decoded_size = ctx->height * stride;
         av_log(ctx->avctx, AV_LOG_WARNING, "Decoded size is too large.\n");
     }
 
@@ -959,8 +957,8 @@ static int old_codec47(SANMVideoContext *ctx, int top,
     }
     if (!seq) {
         ctx->prev_seq = -1;
-        memset(prev1, auxcol[0], (ctx->height - top) * stride);
-        memset(prev2, auxcol[1], (ctx->height - top) * stride);
+        memset(prev1, auxcol[0], ctx->height * stride);
+        memset(prev2, auxcol[1], ctx->height * stride);
     }
 
     switch (compr) {
@@ -1267,9 +1265,9 @@ static int process_frame_obj(SANMVideoContext *ctx)
     case 3:
         return old_codec1(ctx, top, left, w, h);
     case 37:
-        return old_codec37(ctx, top, left, w, h);
+        return old_codec37(ctx, w, h);
     case 47:
-        return old_codec47(ctx, top, left, w, h);
+        return old_codec47(ctx, w, h);
     case 48:
         return old_codec48(ctx, w, h);
     default:
-- 
2.48.1

_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".

^ permalink raw reply	[flat|nested] 15+ messages in thread

* [FFmpeg-devel] [PATCH v2 02/13] avcodec/sanm: FOBJ left/top are signed values
  2025-03-15  9:32 [FFmpeg-devel] [PATCH v2 00/13] avcodec/sanm: various improvements Manuel Lauss
  2025-03-15  9:32 ` [FFmpeg-devel] [PATCH v2 01/13] avcodec/sanm: disable left/top for fullscreen codecs Manuel Lauss
@ 2025-03-15  9:32 ` Manuel Lauss
  2025-03-15  9:32 ` [FFmpeg-devel] [PATCH v2 03/13] avcodec/sanm: better frame size detection for old codecs Manuel Lauss
                   ` (10 subsequent siblings)
  12 siblings, 0 replies; 15+ messages in thread
From: Manuel Lauss @ 2025-03-15  9:32 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: Manuel Lauss

The left/top parameters of a FOBJ are signed values.  Adjust
codec1 code accordingly to not draw outside the buffer area.
Rebel Assault 1 makes heavy use of this.

Signed-off-by: Manuel Lauss <manuel.lauss@gmail.com>
---
 libavcodec/sanm.c | 33 ++++++++++++++++++---------------
 1 file changed, 18 insertions(+), 15 deletions(-)

diff --git a/libavcodec/sanm.c b/libavcodec/sanm.c
index 49ac9bebfe..65ca525b9d 100644
--- a/libavcodec/sanm.c
+++ b/libavcodec/sanm.c
@@ -558,18 +558,18 @@ static int rle_decode(SANMVideoContext *ctx, uint8_t *dst, const int out_size)
 static int old_codec1(SANMVideoContext *ctx, int top,
                       int left, int width, int height)
 {
-    uint8_t *dst = ((uint8_t *)ctx->frm0) + left + top * ctx->pitch;
-    int i, j, len, flag, code, val, pos, end;
+    int i, j, len, flag, code, val, end, pxoff;
+    const int maxpxo = ctx->height * ctx->pitch;
+    uint8_t *dst = (uint8_t *)ctx->frm0;
 
     for (i = 0; i < height; i++) {
-        pos = 0;
-
         if (bytestream2_get_bytes_left(&ctx->gb) < 2)
             return AVERROR_INVALIDDATA;
 
         len = bytestream2_get_le16u(&ctx->gb);
         end = bytestream2_tell(&ctx->gb) + len;
 
+        pxoff = left + ((top + i) * ctx->pitch);
         while (bytestream2_tell(&ctx->gb) < end) {
             if (bytestream2_get_bytes_left(&ctx->gb) < 2)
                 return AVERROR_INVALIDDATA;
@@ -577,25 +577,28 @@ static int old_codec1(SANMVideoContext *ctx, int top,
             code = bytestream2_get_byteu(&ctx->gb);
             flag = code & 1;
             code = (code >> 1) + 1;
-            if (pos + code > width)
-                return AVERROR_INVALIDDATA;
             if (flag) {
                 val = bytestream2_get_byteu(&ctx->gb);
-                if (val)
-                    memset(dst + pos, val, code);
-                pos += code;
+                if (val) {
+                    for (j = 0; j < code; j++) {
+                        if (pxoff >= 0 && pxoff < maxpxo)
+                            *(dst + pxoff) = val;
+                        pxoff++;
+                    }
+                } else {
+                    pxoff += code;
+                }
             } else {
                 if (bytestream2_get_bytes_left(&ctx->gb) < code)
                     return AVERROR_INVALIDDATA;
                 for (j = 0; j < code; j++) {
                     val = bytestream2_get_byteu(&ctx->gb);
-                    if (val)
-                        dst[pos] = val;
-                    pos++;
+                    if ((pxoff >= 0) && (pxoff < maxpxo) && val)
+                        *(dst + pxoff) = val;
+                    pxoff++;
                 }
             }
         }
-        dst += ctx->pitch;
     }
     ctx->rotate_code = 0;
 
@@ -1236,8 +1239,8 @@ static int old_codec48(SANMVideoContext *ctx, int width, int height)
 static int process_frame_obj(SANMVideoContext *ctx)
 {
     uint16_t codec = bytestream2_get_le16u(&ctx->gb);
-    uint16_t left  = bytestream2_get_le16u(&ctx->gb);
-    uint16_t top   = bytestream2_get_le16u(&ctx->gb);
+    int16_t  left  = bytestream2_get_le16u(&ctx->gb);
+    int16_t  top   = bytestream2_get_le16u(&ctx->gb);
     uint16_t w     = bytestream2_get_le16u(&ctx->gb);
     uint16_t h     = bytestream2_get_le16u(&ctx->gb);
 
-- 
2.48.1

_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".

^ permalink raw reply	[flat|nested] 15+ messages in thread

* [FFmpeg-devel] [PATCH v2 03/13] avcodec/sanm: better frame size detection for old codecs
  2025-03-15  9:32 [FFmpeg-devel] [PATCH v2 00/13] avcodec/sanm: various improvements Manuel Lauss
  2025-03-15  9:32 ` [FFmpeg-devel] [PATCH v2 01/13] avcodec/sanm: disable left/top for fullscreen codecs Manuel Lauss
  2025-03-15  9:32 ` [FFmpeg-devel] [PATCH v2 02/13] avcodec/sanm: FOBJ left/top are signed values Manuel Lauss
@ 2025-03-15  9:32 ` Manuel Lauss
  2025-03-15  9:32 ` [FFmpeg-devel] [PATCH v2 04/13] avcodec/sanm: better ANIMv1 engine support Manuel Lauss
                   ` (9 subsequent siblings)
  12 siblings, 0 replies; 15+ messages in thread
From: Manuel Lauss @ 2025-03-15  9:32 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: Manuel Lauss

The size of the video frame (FOBJ) of the old codecs (ANIMv0/1/2) can
very reliably be determined:
- ANIMv0/1 (=Rebel Assault 1) uses a 384x242 internal buffer for
  everything.  The codec parameters only describe the size and offset
  of the specific FOBJ on that buffer.
- ANIMv2 titles usually use one of the fullscreen codecs (37/47/48)
  as first FOBJ, and their dimensions can generally be trusted.
- RA2 uses 424x260 as internal buffer, use that if encountered:
  08PLAY.SAN does not use codec37 so we need to guess using the
  codec coordinates.
- ignore sizes smaller than 2x2 or larger than 800x600.
- some game videos have an initial fobj with either 1x1 or -1x-1
  pixels in size, ignore them with a warning (Full Throttle
  and the Rebel Assault 2 xxRETRY.SAN videos).

Once a known/valid dimension set has been discovered, use it and
don't change it for subsequent FOBJs, rather clamp the large frame
to the determined dimensions.

Tested with RA1, RA2, Full Throttle, Dig, Outlaws, SotE and MotS
videos.

Signed-off-by: Manuel Lauss <manuel.lauss@gmail.com>
---
 libavcodec/sanm.c | 63 ++++++++++++++++++++++++++++++++++++-----------
 1 file changed, 49 insertions(+), 14 deletions(-)

diff --git a/libavcodec/sanm.c b/libavcodec/sanm.c
index 65ca525b9d..38cdb533eb 100644
--- a/libavcodec/sanm.c
+++ b/libavcodec/sanm.c
@@ -264,7 +264,7 @@ typedef struct SANMVideoContext {
     AVCodecContext *avctx;
     GetByteContext gb;
 
-    int version, subversion;
+    int version, subversion, have_dimensions;
     uint32_t pal[PALETTE_SIZE];
     int16_t delta_pal[PALETTE_DELTA];
 
@@ -1244,21 +1244,56 @@ static int process_frame_obj(SANMVideoContext *ctx)
     uint16_t w     = bytestream2_get_le16u(&ctx->gb);
     uint16_t h     = bytestream2_get_le16u(&ctx->gb);
 
-    if (!w || !h) {
-        av_log(ctx->avctx, AV_LOG_ERROR, "Dimensions are invalid.\n");
-        return AVERROR_INVALIDDATA;
+    if (w < 1 || h < 1 || w > 800 || h > 600 || left > 800 || top > 600) {
+        av_log(ctx->avctx, AV_LOG_WARNING,
+               "ignoring invalid fobj dimensions: c%d %d %d @ %d %d\n",
+               codec, w, h, left, top);
+        return 0;
     }
 
-    if (ctx->width < left + w || ctx->height < top + h) {
-        int ret = ff_set_dimensions(ctx->avctx, FFMAX(left + w, ctx->width),
-                                    FFMAX(top + h, ctx->height));
-        if (ret < 0)
-            return ret;
-        init_sizes(ctx, FFMAX(left + w, ctx->width),
-                   FFMAX(top + h, ctx->height));
-        if (init_buffers(ctx)) {
-            av_log(ctx->avctx, AV_LOG_ERROR, "Error resizing buffers.\n");
-            return AVERROR(ENOMEM);
+    if (!ctx->have_dimensions) {
+        int xres, yres;
+        if (ctx->subversion < 2) {
+            /* Rebel Assault 1: 384x242 internal size */
+            xres = 384;
+            yres = 242;
+            ctx->have_dimensions = 1;
+        } else if (codec == 37 || codec == 47 || codec == 48) {
+            /* these codecs work on full frames, trust their dimensions */
+            xres = w;
+            yres = h;
+            ctx->have_dimensions = 1;
+        } else {
+            /* Rebel Assault 2: 424x260 internal size */
+            if (((left + w) == 424) && ((top + h) == 260))
+                ctx->have_dimensions = 1;
+
+            xres = FFMAX(left + w, ctx->width);
+            yres = FFMAX(top + h, ctx->height);
+        }
+
+        if (ctx->width < xres || ctx->height < yres) {
+            int ret = ff_set_dimensions(ctx->avctx, xres, yres);
+            if (ret < 0)
+                return ret;
+            init_sizes(ctx, xres, yres);
+            if (init_buffers(ctx)) {
+                av_log(ctx->avctx, AV_LOG_ERROR, "Error resizing buffers.\n");
+                return AVERROR(ENOMEM);
+            }
+        }
+    } else {
+        if (((left + w > ctx->width) || (top + h > ctx->height))
+            && (codec >= 37)) {
+            /* correct unexpected overly large frames: this happens
+             * for instance with The Dig's sq1.san video: it has a few
+             * (all black) 640x480 frames halfway in, while the rest is
+             * 320x200.
+             */
+            av_log(ctx->avctx, AV_LOG_WARNING,
+                   "resizing too large fobj: c%d  %d %d @ %d %d\n", codec, w, h, left, top);
+            w = ctx->width;
+            h = ctx->height;
         }
     }
     bytestream2_skip(&ctx->gb, 4);
-- 
2.48.1

_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".

^ permalink raw reply	[flat|nested] 15+ messages in thread

* [FFmpeg-devel] [PATCH v2 04/13] avcodec/sanm: better ANIMv1 engine support
  2025-03-15  9:32 [FFmpeg-devel] [PATCH v2 00/13] avcodec/sanm: various improvements Manuel Lauss
                   ` (2 preceding siblings ...)
  2025-03-15  9:32 ` [FFmpeg-devel] [PATCH v2 03/13] avcodec/sanm: better frame size detection for old codecs Manuel Lauss
@ 2025-03-15  9:32 ` Manuel Lauss
  2025-03-15  9:32 ` [FFmpeg-devel] [PATCH v2 05/13] avcodec/sanm: fix codec3 Manuel Lauss
                   ` (8 subsequent siblings)
  12 siblings, 0 replies; 15+ messages in thread
From: Manuel Lauss @ 2025-03-15  9:32 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: Manuel Lauss

- clear the front buffer with color 0 on the first FOBJ.
  Fixes a lot of Rebel Assault 1 videos and Rebel Assault 2 space
  scenes (e.g. 08PLAY.SAN which consists only of codec1/2/21 objects
  which only ever touch parts of the buffer).
- for ANIMv1 (Rebel Assault 1): set palette index 0 to all zeroes.
  This fixes a lot of stray colors in e.g L1HANGAR.ANM, L2INTRO.ANM,
  space scenes.
- Esp in RA1, there are a lot of FRME objects which don't contain
  any video data (prebuffering some audio only). Account for that.
- In RA1 L2PLAY.ANM there are a few unaligned FOBJs, handle this
  in a generic way.

Signed-off-by: Manuel Lauss <manuel.lauss@gmail.com>
---
v2: added handling of unaligned objects

 libavcodec/sanm.c | 49 ++++++++++++++++++++++++++++++++++++++---------
 1 file changed, 40 insertions(+), 9 deletions(-)

diff --git a/libavcodec/sanm.c b/libavcodec/sanm.c
index 38cdb533eb..069bc0400a 100644
--- a/libavcodec/sanm.c
+++ b/libavcodec/sanm.c
@@ -264,7 +264,7 @@ typedef struct SANMVideoContext {
     AVCodecContext *avctx;
     GetByteContext gb;
 
-    int version, subversion, have_dimensions;
+    int version, subversion, have_dimensions, first_fob;
     uint32_t pal[PALETTE_SIZE];
     int16_t delta_pal[PALETTE_DELTA];
 
@@ -515,6 +515,8 @@ static av_cold int decode_init(AVCodecContext *avctx)
         ctx->subversion = AV_RL16(avctx->extradata);
         for (i = 0; i < PALETTE_SIZE; i++)
             ctx->pal[i] = 0xFFU << 24 | AV_RL32(avctx->extradata + 2 + i * 4);
+        if (ctx->subversion < 2)
+            ctx->pal[0] = 0xFFU << 24;
     }
 
     return 0;
@@ -1298,6 +1300,15 @@ static int process_frame_obj(SANMVideoContext *ctx)
     }
     bytestream2_skip(&ctx->gb, 4);
 
+    /* on first FOBJ, when the codec is not one of the
+     * full-buffer codecs (37/47/48), frm0 needs to be cleared.
+     */
+    if (ctx->first_fob) {
+        ctx->first_fob = 0;
+        if (codec < 37)
+            memset(ctx->frm0, 0, ctx->frm0_size);
+    }
+
     switch (codec) {
     case 1:
     case 3:
@@ -1349,6 +1360,8 @@ static int process_xpal(SANMVideoContext *ctx, int size)
         if (size >= PALETTE_DELTA * 2 + 4 + PALETTE_SIZE * 3) {
             for (i = 0; i < PALETTE_SIZE; i++)
                 ctx->pal[i] = 0xFFU << 24 | bytestream2_get_be24u(&ctx->gb);
+            if (ctx->subversion < 2)
+                ctx->pal[0] = 0xFFU << 24;
         }
     }
     return 0;
@@ -1762,7 +1775,9 @@ static int decode_frame(AVCodecContext *avctx, AVFrame *frame,
     bytestream2_init(&ctx->gb, pkt->data, pkt->size);
 
     if (!ctx->version) {
-        int to_store = 0;
+        int to_store = 0, have_img = 0;
+
+        ctx->first_fob = 1;
 
         while (bytestream2_get_bytes_left(&ctx->gb) >= 8) {
             uint32_t sig, size;
@@ -1785,12 +1800,15 @@ static int decode_frame(AVCodecContext *avctx, AVFrame *frame,
                 }
                 for (i = 0; i < PALETTE_SIZE; i++)
                     ctx->pal[i] = 0xFFU << 24 | bytestream2_get_be24u(&ctx->gb);
+                if (ctx->subversion < 2)
+                    ctx->pal[0] = 0xFFU << 24;
                 break;
             case MKBETAG('F', 'O', 'B', 'J'):
                 if (size < 16)
                     return AVERROR_INVALIDDATA;
                 if (ret = process_frame_obj(ctx))
                     return ret;
+                have_img = 1;
                 break;
             case MKBETAG('X', 'P', 'A', 'L'):
                 if (ret = process_xpal(ctx, size))
@@ -1801,6 +1819,7 @@ static int decode_frame(AVCodecContext *avctx, AVFrame *frame,
                 break;
             case MKBETAG('F', 'T', 'C', 'H'):
                 memcpy(ctx->frm0, ctx->stored_frame, ctx->buf_size);
+                have_img = 1;
                 break;
             default:
                 bytestream2_skip(&ctx->gb, size);
@@ -1809,15 +1828,26 @@ static int decode_frame(AVCodecContext *avctx, AVFrame *frame,
                 break;
             }
 
+            /* the sizes of chunks are usually a multiple of 2. However
+             * there are a few unaligned FOBJs in RA1 L2PLAY.ANM only (looks
+             * like a game bug) and IACT audio chunks which have odd sizes
+             * but are padded with a zero byte.
+             */
             bytestream2_seek(&ctx->gb, pos + size, SEEK_SET);
-            if (size & 1)
-                bytestream2_skip(&ctx->gb, 1);
+            if ((pos + size) & 1) {
+                if (0 != bytestream2_get_byteu(&ctx->gb))
+                    bytestream2_seek(&ctx->gb, pos + size, SEEK_SET);
+            }
         }
         if (to_store)
             memcpy(ctx->stored_frame, ctx->frm0, ctx->buf_size);
-        if ((ret = copy_output(ctx, NULL)))
-            return ret;
-        memcpy(ctx->frame->data[1], ctx->pal, 1024);
+
+        if (have_img && ctx->have_dimensions) {
+            if ((ret = copy_output(ctx, NULL)))
+                return ret;
+            memcpy(ctx->frame->data[1], ctx->pal, 1024);
+            *got_frame_ptr = 1;
+        }
     } else {
         SANMFrameHeader header;
 
@@ -1848,12 +1878,13 @@ static int decode_frame(AVCodecContext *avctx, AVFrame *frame,
 
         if ((ret = copy_output(ctx, &header)))
             return ret;
+
+        *got_frame_ptr = 1;
+
     }
     if (ctx->rotate_code)
         rotate_bufs(ctx, ctx->rotate_code);
 
-    *got_frame_ptr = 1;
-
     return pkt->size;
 }
 
-- 
2.48.1

_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".

^ permalink raw reply	[flat|nested] 15+ messages in thread

* [FFmpeg-devel] [PATCH v2 05/13] avcodec/sanm: fix codec3
  2025-03-15  9:32 [FFmpeg-devel] [PATCH v2 00/13] avcodec/sanm: various improvements Manuel Lauss
                   ` (3 preceding siblings ...)
  2025-03-15  9:32 ` [FFmpeg-devel] [PATCH v2 04/13] avcodec/sanm: better ANIMv1 engine support Manuel Lauss
@ 2025-03-15  9:32 ` Manuel Lauss
  2025-03-15  9:32 ` [FFmpeg-devel] [PATCH v2 06/13] avcodec/sanm: codec2 decoder Manuel Lauss
                   ` (7 subsequent siblings)
  12 siblings, 0 replies; 15+ messages in thread
From: Manuel Lauss @ 2025-03-15  9:32 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: Manuel Lauss

codec3 is codec1 which writes zero values instead of skipping them.
This fixes a lot of RA1 videos.

Signed-off-by: Manuel Lauss <manuel.lauss@gmail.com>
---
 libavcodec/sanm.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/libavcodec/sanm.c b/libavcodec/sanm.c
index 069bc0400a..0c8ca73508 100644
--- a/libavcodec/sanm.c
+++ b/libavcodec/sanm.c
@@ -558,7 +558,7 @@ static int rle_decode(SANMVideoContext *ctx, uint8_t *dst, const int out_size)
 }
 
 static int old_codec1(SANMVideoContext *ctx, int top,
-                      int left, int width, int height)
+                      int left, int width, int height, int opaque)
 {
     int i, j, len, flag, code, val, end, pxoff;
     const int maxpxo = ctx->height * ctx->pitch;
@@ -581,7 +581,7 @@ static int old_codec1(SANMVideoContext *ctx, int top,
             code = (code >> 1) + 1;
             if (flag) {
                 val = bytestream2_get_byteu(&ctx->gb);
-                if (val) {
+                if (val || opaque) {
                     for (j = 0; j < code; j++) {
                         if (pxoff >= 0 && pxoff < maxpxo)
                             *(dst + pxoff) = val;
@@ -595,7 +595,7 @@ static int old_codec1(SANMVideoContext *ctx, int top,
                     return AVERROR_INVALIDDATA;
                 for (j = 0; j < code; j++) {
                     val = bytestream2_get_byteu(&ctx->gb);
-                    if ((pxoff >= 0) && (pxoff < maxpxo) && val)
+                    if ((pxoff >= 0) && (pxoff < maxpxo) && (val || opaque))
                         *(dst + pxoff) = val;
                     pxoff++;
                 }
@@ -1312,7 +1312,7 @@ static int process_frame_obj(SANMVideoContext *ctx)
     switch (codec) {
     case 1:
     case 3:
-        return old_codec1(ctx, top, left, w, h);
+        return old_codec1(ctx, top, left, w, h, codec == 3);
     case 37:
         return old_codec37(ctx, w, h);
     case 47:
-- 
2.48.1

_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".

^ permalink raw reply	[flat|nested] 15+ messages in thread

* [FFmpeg-devel] [PATCH v2 06/13] avcodec/sanm: codec2 decoder
  2025-03-15  9:32 [FFmpeg-devel] [PATCH v2 00/13] avcodec/sanm: various improvements Manuel Lauss
                   ` (4 preceding siblings ...)
  2025-03-15  9:32 ` [FFmpeg-devel] [PATCH v2 05/13] avcodec/sanm: fix codec3 Manuel Lauss
@ 2025-03-15  9:32 ` Manuel Lauss
  2025-03-15  9:32 ` [FFmpeg-devel] [PATCH v2 07/13] avcodec/sanm: codec23 decoder Manuel Lauss
                   ` (6 subsequent siblings)
  12 siblings, 0 replies; 15+ messages in thread
From: Manuel Lauss @ 2025-03-15  9:32 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: Manuel Lauss

this codec consists of 4 byte packets: 2bytes delta-x, 1 byte delta-y
and 1 byte color to put at that spot.
Used in Rebel Assault 1 only.

Signed-off-by: Manuel Lauss <manuel.lauss@gmail.com>
---
 libavcodec/sanm.c | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/libavcodec/sanm.c b/libavcodec/sanm.c
index 0c8ca73508..c5099dc999 100644
--- a/libavcodec/sanm.c
+++ b/libavcodec/sanm.c
@@ -607,6 +607,24 @@ static int old_codec1(SANMVideoContext *ctx, int top,
     return 0;
 }
 
+static int old_codec2(SANMVideoContext *ctx, int top,
+                      int left, int width, int height)
+{
+    uint8_t *dst = (uint8_t *)ctx->frm0, col;
+    int16_t xpos = left, ypos = top;
+
+    while (bytestream2_get_bytes_left(&ctx->gb) > 3) {
+        xpos += bytestream2_get_le16u(&ctx->gb);
+        ypos += bytestream2_get_byteu(&ctx->gb);
+        col = bytestream2_get_byteu(&ctx->gb);
+        if (xpos >= 0 && ypos >= 0 &&
+            xpos < ctx->width && ypos < ctx->height) {
+                *(dst + xpos + ypos * ctx->pitch) = col;
+        }
+    }
+    return 0;
+}
+
 static inline void codec37_mv(uint8_t *dst, const uint8_t *src,
                               int height, int stride, int x, int y)
 {
@@ -1313,6 +1331,8 @@ static int process_frame_obj(SANMVideoContext *ctx)
     case 1:
     case 3:
         return old_codec1(ctx, top, left, w, h, codec == 3);
+    case 2:
+        return old_codec2(ctx, top, left, w, h);
     case 37:
         return old_codec37(ctx, w, h);
     case 47:
-- 
2.48.1

_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".

^ permalink raw reply	[flat|nested] 15+ messages in thread

* [FFmpeg-devel] [PATCH v2 07/13] avcodec/sanm: codec23 decoder
  2025-03-15  9:32 [FFmpeg-devel] [PATCH v2 00/13] avcodec/sanm: various improvements Manuel Lauss
                   ` (5 preceding siblings ...)
  2025-03-15  9:32 ` [FFmpeg-devel] [PATCH v2 06/13] avcodec/sanm: codec2 decoder Manuel Lauss
@ 2025-03-15  9:32 ` Manuel Lauss
  2025-03-15  9:32 ` [FFmpeg-devel] [PATCH v2 08/13] avcodec/sanm: codec21 decoder Manuel Lauss
                   ` (5 subsequent siblings)
  12 siblings, 0 replies; 15+ messages in thread
From: Manuel Lauss @ 2025-03-15  9:32 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: Manuel Lauss

This codec alternatingly skips and changes existing pixels.
A second 16bit parameter in the FOBJ header indicates how to do
the pixel changes: either by specifying a LUT in the codec datastream
or by adding a constant value to the pixel.

Signed-off-by: Manuel Lauss <manuel.lauss@gmail.com>
---
 libavcodec/sanm.c | 66 +++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 64 insertions(+), 2 deletions(-)

diff --git a/libavcodec/sanm.c b/libavcodec/sanm.c
index c5099dc999..aca5bf49ec 100644
--- a/libavcodec/sanm.c
+++ b/libavcodec/sanm.c
@@ -292,6 +292,7 @@ typedef struct SANMVideoContext {
     int8_t p4x4glyphs[NGLYPHS][16];
     int8_t p8x8glyphs[NGLYPHS][64];
     uint8_t c47itbl[0x10000];
+    uint8_t c23lut[256];
 } SANMVideoContext;
 
 typedef struct SANMFrameHeader {
@@ -557,6 +558,62 @@ static int rle_decode(SANMVideoContext *ctx, uint8_t *dst, const int out_size)
     return 0;
 }
 
+static int old_codec23(SANMVideoContext *ctx, int top, int left, int width,
+                       int height, uint8_t param, uint16_t param2)
+{
+    const uint32_t maxpxo = ctx->height * ctx->pitch;
+    uint8_t *dst, lut[256], c;
+    int i, j, k, pc, sk;
+    int32_t pxoff;
+
+    if (ctx->subversion < 2) {
+        /* Rebel Assault 1: constant offset + 0xd0 */
+        for (i = 0; i < 256; i++)
+            lut[i] = (i + param + 0xd0) & 0xff;
+    } else if (param2 == 256) {
+        if (bytestream2_get_bytes_left(&ctx->gb) < 256)
+            return AVERROR_INVALIDDATA;
+        bytestream2_get_bufferu(&ctx->gb, ctx->c23lut, 256);
+    } else if (param2 < 256) {
+        for (i = 0; i < 256; i++)
+            lut[i] = (i + param2) & 0xff;
+    } else {
+        memcpy(lut, ctx->c23lut, 256);
+    }
+    if (bytestream2_get_bytes_left(&ctx->gb) < 1)
+        return 0;  /* some c23 frames just set up the LUT */
+
+    dst = (uint8_t *)ctx->frm0;
+    for (i = 0; i < height; i++) {
+        if (bytestream2_get_bytes_left(&ctx->gb) < 2)
+            return 0;
+        pxoff = left + ((top + i) * ctx->pitch);
+        k = bytestream2_get_le16u(&ctx->gb);
+        sk = 1;
+        pc = 0;
+        while (k > 0 && pc <= width) {
+            if (bytestream2_get_bytes_left(&ctx->gb) < 1)
+                return AVERROR_INVALIDDATA;
+            j = bytestream2_get_byteu(&ctx->gb);
+            if (sk) {
+                pxoff += j;
+                pc += j;
+            } else {
+                while (j--) {
+                    if (pxoff >=0 && pxoff < maxpxo) {
+                        c = *(dst + pxoff);
+                        *(dst + pxoff) = lut[c];
+                    }
+                    pxoff++;
+                    pc++;
+                }
+            }
+            sk ^= 1;
+        }
+    }
+    return 0;
+}
+
 static int old_codec1(SANMVideoContext *ctx, int top,
                       int left, int width, int height, int opaque)
 {
@@ -1258,11 +1315,15 @@ static int old_codec48(SANMVideoContext *ctx, int width, int height)
 
 static int process_frame_obj(SANMVideoContext *ctx)
 {
-    uint16_t codec = bytestream2_get_le16u(&ctx->gb);
+    uint16_t parm2;
+    uint8_t  codec = bytestream2_get_byteu(&ctx->gb);
+    uint8_t  param = bytestream2_get_byteu(&ctx->gb);
     int16_t  left  = bytestream2_get_le16u(&ctx->gb);
     int16_t  top   = bytestream2_get_le16u(&ctx->gb);
     uint16_t w     = bytestream2_get_le16u(&ctx->gb);
     uint16_t h     = bytestream2_get_le16u(&ctx->gb);
+    bytestream2_skip(&ctx->gb, 2);
+    parm2 = bytestream2_get_le16u(&ctx->gb);
 
     if (w < 1 || h < 1 || w > 800 || h > 600 || left > 800 || top > 600) {
         av_log(ctx->avctx, AV_LOG_WARNING,
@@ -1316,7 +1377,6 @@ static int process_frame_obj(SANMVideoContext *ctx)
             h = ctx->height;
         }
     }
-    bytestream2_skip(&ctx->gb, 4);
 
     /* on first FOBJ, when the codec is not one of the
      * full-buffer codecs (37/47/48), frm0 needs to be cleared.
@@ -1333,6 +1393,8 @@ static int process_frame_obj(SANMVideoContext *ctx)
         return old_codec1(ctx, top, left, w, h, codec == 3);
     case 2:
         return old_codec2(ctx, top, left, w, h);
+    case 23:
+        return old_codec23(ctx, top, left, w, h, param, parm2);
     case 37:
         return old_codec37(ctx, w, h);
     case 47:
-- 
2.48.1

_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".

^ permalink raw reply	[flat|nested] 15+ messages in thread

* [FFmpeg-devel] [PATCH v2 08/13] avcodec/sanm: codec21 decoder
  2025-03-15  9:32 [FFmpeg-devel] [PATCH v2 00/13] avcodec/sanm: various improvements Manuel Lauss
                   ` (6 preceding siblings ...)
  2025-03-15  9:32 ` [FFmpeg-devel] [PATCH v2 07/13] avcodec/sanm: codec23 decoder Manuel Lauss
@ 2025-03-15  9:32 ` Manuel Lauss
  2025-03-15  9:32 ` [FFmpeg-devel] [PATCH v2 09/13] avcodec/sanm: codec4/5/33/34 decoder Manuel Lauss
                   ` (4 subsequent siblings)
  12 siblings, 0 replies; 15+ messages in thread
From: Manuel Lauss @ 2025-03-15  9:32 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: Manuel Lauss

similar to codec23, this one alternatingly skips and writes bytes.

Signed-off-by: Manuel Lauss <manuel.lauss@gmail.com>
---
 libavcodec/sanm.c | 45 +++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 45 insertions(+)

diff --git a/libavcodec/sanm.c b/libavcodec/sanm.c
index aca5bf49ec..6080144235 100644
--- a/libavcodec/sanm.c
+++ b/libavcodec/sanm.c
@@ -614,6 +614,49 @@ static int old_codec23(SANMVideoContext *ctx, int top, int left, int width,
     return 0;
 }
 
+static int old_codec21(SANMVideoContext *ctx, int top, int left, int width,
+                       int height)
+{
+    const uint32_t maxpxo = ctx->height * ctx->pitch;
+    uint8_t *dst = (uint8_t *)ctx->frm0, c;
+    int i, j, k, pc, sk, pxoff;
+
+    dst = (uint8_t *)ctx->frm0;
+    for (i = 0; i < height; i++) {
+        if (bytestream2_get_bytes_left(&ctx->gb) < 2)
+            return 0;
+        pxoff = left + ((top + i) * ctx->pitch);
+        k = bytestream2_get_le16u(&ctx->gb);
+        sk = 1;
+        pc = 0;
+        while (k > 0 && pc <= width) {
+            if (bytestream2_get_bytes_left(&ctx->gb) < 2)
+                return AVERROR_INVALIDDATA;
+            j = bytestream2_get_le16u(&ctx->gb);
+            k -= 2;
+            if (sk) {
+                pxoff += j;
+                pc += j;
+            } else {
+                if (bytestream2_get_bytes_left(&ctx->gb) < (j + 1))
+                    return AVERROR_INVALIDDATA;
+                do {
+                    c = bytestream2_get_byteu(&ctx->gb);
+                    if (pxoff >=0 && pxoff < maxpxo) {
+                        *(dst + pxoff) = c;
+                    }
+                    pxoff++;
+                    pc++;
+                    j--;
+                    k--;
+                } while (j > -1);
+            }
+            sk ^= 1;
+        }
+    }
+    return 0;
+}
+
 static int old_codec1(SANMVideoContext *ctx, int top,
                       int left, int width, int height, int opaque)
 {
@@ -1393,6 +1436,8 @@ static int process_frame_obj(SANMVideoContext *ctx)
         return old_codec1(ctx, top, left, w, h, codec == 3);
     case 2:
         return old_codec2(ctx, top, left, w, h);
+    case 21:
+        return old_codec21(ctx, top, left, w, h);
     case 23:
         return old_codec23(ctx, top, left, w, h, param, parm2);
     case 37:
-- 
2.48.1

_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".

^ permalink raw reply	[flat|nested] 15+ messages in thread

* [FFmpeg-devel] [PATCH v2 09/13] avcodec/sanm: codec4/5/33/34 decoder
  2025-03-15  9:32 [FFmpeg-devel] [PATCH v2 00/13] avcodec/sanm: various improvements Manuel Lauss
                   ` (7 preceding siblings ...)
  2025-03-15  9:32 ` [FFmpeg-devel] [PATCH v2 08/13] avcodec/sanm: codec21 decoder Manuel Lauss
@ 2025-03-15  9:32 ` Manuel Lauss
  2025-03-15  9:32 ` [FFmpeg-devel] [PATCH v2 10/13] avcodec/sanm: codec37: reimplement comp4 Manuel Lauss
                   ` (3 subsequent siblings)
  12 siblings, 0 replies; 15+ messages in thread
From: Manuel Lauss @ 2025-03-15  9:32 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: Manuel Lauss

This codec works on 4x4 pixel tiles, which can be generated and also
read from the datastream.  Codec5 does not recognize the "skip-block"
command; codecs33/34 are the same as 4/5 but with a different tileset
generator.

Signed-off-by: Manuel Lauss <manuel.lauss@gmail.com>
---
v2: had left+top swapped resulted in weird visuals in e.g. L5PLAY.ANM

codec33/34 are untested since I have no video files using it.  I think
they are used only in the SEGA-CD Version of Rebel Assault 1, along
with codec31/32 which are modifications of codec1.  But the latest DOS
EXE had the code in it so I implemented it here as well.

 libavcodec/sanm.c | 229 ++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 229 insertions(+)

diff --git a/libavcodec/sanm.c b/libavcodec/sanm.c
index 6080144235..eecfefd43b 100644
--- a/libavcodec/sanm.c
+++ b/libavcodec/sanm.c
@@ -293,6 +293,8 @@ typedef struct SANMVideoContext {
     int8_t p8x8glyphs[NGLYPHS][64];
     uint8_t c47itbl[0x10000];
     uint8_t c23lut[256];
+    uint8_t c4tbl[2][256][16];
+    uint16_t c4param;
 } SANMVideoContext;
 
 typedef struct SANMFrameHeader {
@@ -480,6 +482,142 @@ static av_cold int init_buffers(SANMVideoContext *ctx)
     return 0;
 }
 
+static void codec33_gen_tiles(SANMVideoContext *ctx, int8_t param1)
+{
+    uint8_t *dst = &(ctx->c4tbl[0][0][0]);
+    int i, j, k, l, m, n, o, p;
+
+    for (i = 0; i < 8; i++) {
+        for (k = 0; k < 8; k++) {
+            j = i + param1;
+            l = k + param1;
+            p = (j + k) / 2;
+            j = (j + p) / 2;
+            m = l / 2;
+            n = (i + param1);
+            o = (k + param1);
+
+            *dst++ = p; *dst++ = p; *dst++ = j; *dst++ = n;
+            *dst++ = p; *dst++ = p; *dst++ = j; *dst++ = i;
+            *dst++ = m; *dst++ = m; *dst++ = p; *dst++ = j;
+            *dst++ = l; *dst++ = l; *dst++ = m; *dst++ = p;
+        }
+    }
+
+    for (i = 0; i < 8; i++) {
+        for (k = 0; k < 8; k++) {
+            j = i + param1;
+            l = k + param1;
+            n = ((j + l) / 2);
+            m = ((l + n) / 2);
+
+            *dst++ = j; *dst++ = j; *dst++ = j; *dst++ = j;
+            *dst++ = n; *dst++ = n; *dst++ = n; *dst++ = n;
+            *dst++ = m; *dst++ = m; *dst++ = m; *dst++ = m;
+            *dst++ = l; *dst++ = l; *dst++ = l; *dst++ = l;
+        }
+    }
+
+    for (i = 0; i < 8; i++) {
+        for (k = 0; k < 8; k++) {
+            j = i + param1;
+            l = k + param1;
+            m = (j + l) / 2;
+            n = (j + m) / 2;
+            o = m / 2;
+            p = j & 0xff;
+
+            *dst++ = p; *dst++ = p; *dst++ = n; *dst++ = m;
+            *dst++ = p; *dst++ = p; *dst++ = n; *dst++ = m;
+            *dst++ = n; *dst++ = n; *dst++ = m; *dst++ = o;
+            *dst++ = m; *dst++ = m; *dst++ = o; *dst++ = l;
+        }
+    }
+
+    for (i = 0; i < 8; i++) {
+        for (k = 0; k < 8; k++) {
+            j = i + param1;
+            l = k + param1;
+            m = (j + l) / 2;
+            n = m / 2;
+
+            *dst++ = j; *dst++ = m; *dst++ = n; *dst++ = l;
+            *dst++ = j; *dst++ = m; *dst++ = n; *dst++ = l;
+            *dst++ = j; *dst++ = m; *dst++ = n; *dst++ = l;
+            *dst++ = j; *dst++ = m; *dst++ = n; *dst++ = l;
+        }
+    }
+}
+
+static void codec4_gen_tiles(SANMVideoContext *ctx, uint16_t param1)
+{
+    uint8_t *dst = &(ctx->c4tbl[0][0][0]);
+    int i, j, k, l, m, n, o;
+
+    for (i = 1; i < 16; i += 2) {
+        for (k = 0; k < 16; k++) {
+            j = i + param1;
+            l = k + param1;
+            m = (j + l) / 2;
+            n = (j + m) / 2;
+            o = (l + m) / 2;
+            if (j == m || l == m) {
+                *dst++ = l; *dst++ = j; *dst++ = l; *dst++ = j;
+                *dst++ = j; *dst++ = l; *dst++ = j; *dst++ = j;
+                *dst++ = l; *dst++ = j; *dst++ = l; *dst++ = j;
+                *dst++ = l; *dst++ = l; *dst++ = j; *dst++ = l;
+            } else {
+                *dst++ = m; *dst++ = m; *dst++ = n; *dst++ = j;
+                *dst++ = m; *dst++ = m; *dst++ = n; *dst++ = j;
+                *dst++ = o; *dst++ = o; *dst++ = m; *dst++ = n;
+                *dst++ = l; *dst++ = l; *dst++ = o; *dst++ = m;
+            }
+        }
+    }
+
+    for (i = 0; i < 16; i += 2) {
+        for (k = 0; k < 16; k++) {
+            j = i + param1;
+            l = k + param1;
+            m = (j + l) / 2;
+            n = (j + m) / 2;
+            o = (l + m) / 2;
+            if (m == j || m == l) {
+                *dst++ = j; *dst++ = j; *dst++ = l; *dst++ = j;
+                *dst++ = j; *dst++ = j; *dst++ = j; *dst++ = l;
+                *dst++ = l; *dst++ = j; *dst++ = l; *dst++ = l;
+                *dst++ = j; *dst++ = l; *dst++ = j; *dst++ = l;
+            } else {
+                *dst++ = j; *dst++ = j; *dst++ = n; *dst++ = m;
+                *dst++ = j; *dst++ = j; *dst++ = n; *dst++ = m;
+                *dst++ = n; *dst++ = n; *dst++ = m; *dst++ = o;
+                *dst++ = m; *dst++ = m; *dst++ = o; *dst++ = l;
+            }
+        }
+    }
+}
+
+
+static int codec4_load_tiles(SANMVideoContext *ctx, uint16_t param2, uint8_t clr)
+{
+    uint8_t c, *dst = (uint8_t *)&(ctx->c4tbl[1][0][0]);
+    uint32_t loop = param2 * 8;
+
+    if (param2 > 256)
+        return AVERROR_INVALIDDATA;
+
+    if (bytestream2_get_bytes_left(&ctx->gb) < loop)
+        return AVERROR_INVALIDDATA;
+
+    while (loop--) {
+        c = bytestream2_get_byteu(&ctx->gb);
+        *dst++ = (c >> 4) + clr;
+        *dst++ = (c & 0xf) + clr;
+    }
+
+    return 0;
+}
+
 static void rotate_bufs(SANMVideoContext *ctx, int rotate_code)
 {
     if (rotate_code == 2)
@@ -519,6 +657,7 @@ static av_cold int decode_init(AVCodecContext *avctx)
         if (ctx->subversion < 2)
             ctx->pal[0] = 0xFFU << 24;
     }
+    ctx->c4param = 0xffff;
 
     return 0;
 }
@@ -532,6 +671,91 @@ static av_cold int decode_end(AVCodecContext *avctx)
     return 0;
 }
 
+static int old_codec4(SANMVideoContext *ctx, int top, int left, int w, int h,
+                      uint8_t param, uint16_t param2, int codec)
+{
+    const uint16_t p = ctx->pitch;
+    const uint32_t maxpxo = ctx->height * p;
+    uint8_t mask, bits, idx, *gs, *dst = (uint8_t *)ctx->frm0;
+    int i, j, k, l, bit, ret;
+    int32_t pxoff, pxo2;
+
+    if (ctx->c4param != param) {
+        if (codec > 32)
+            codec33_gen_tiles(ctx, param);
+        else
+            codec4_gen_tiles(ctx, param);
+        ctx->c4param = param;
+    }
+    if (param2 > 0) {
+        ret = codec4_load_tiles(ctx, param2, param);
+        if (ret)
+            return ret;
+    }
+
+    if (codec > 32)
+        codec -= 29;
+
+    for (j = 0; j < w; j += 4) {
+        mask = bits = 0;
+        for (i = 0; i < h; i += 4) {
+            pxoff = j + left + ((top + i) * p);
+            if (param2 > 0) {
+                if (bits == 0) {
+                    if (bytestream2_get_bytes_left(&ctx->gb) < 1)
+                        return AVERROR_INVALIDDATA;
+                    mask = bytestream2_get_byteu(&ctx->gb);
+                    bits = 8;
+                }
+                bit = !!(mask & 0x80);
+                mask <<= 1;
+                bits--;
+            } else {
+                bit = 0;
+            }
+
+            if (bytestream2_get_bytes_left(&ctx->gb) < 1)
+                return AVERROR_INVALIDDATA;
+            idx = bytestream2_get_byteu(&ctx->gb);
+            if ((bit == 0) && (idx == 0x80) && (codec != 5))
+                continue;
+
+            gs = &(ctx->c4tbl[bit][idx][0]);
+            pxo2 = pxoff;
+            for (k = 0; k < 4; k++) {
+                for (l = 0; l < 4; l++) {
+                    if (pxo2 >= 0 && pxo2 < maxpxo) {
+                        *(dst + pxo2) = *gs;
+                    }
+                    gs++;
+                    pxo2++;
+                }
+                pxo2 = pxo2 - 4 + p;
+            }
+
+            /* smooth top and left block borders with neighbours */
+            if (((pxoff - p + k) < 0) || ((pxoff - p + k) >= maxpxo)
+                || ((pxoff + 3 * p) < 0) || ((pxoff + 3 * p) >= maxpxo)
+                || (i == 0) || (j == 0))
+                continue;
+            if (param & 0x80) {
+                for (k = 0; k < 4; k++)
+                    *(dst + pxoff + k) = ((*(dst + pxoff + k) + *(dst + pxoff - p + k)) >> 1) | 0x80;
+                *(dst + pxoff + 1 * p) = (*(dst + pxoff + 1 * p) + *(dst + pxoff + 1 * p - 1)) >> 1 | 0x80;
+                *(dst + pxoff + 2 * p) = (*(dst + pxoff + 2 * p) + *(dst + pxoff + 2 * p - 1)) >> 1 | 0x80;
+                *(dst + pxoff + 3 * p) = (*(dst + pxoff + 3 * p) + *(dst + pxoff + 3 * p - 1)) >> 1 | 0x80;
+            } else {
+                for (k = 0; k < 4; k++)
+                    *(dst + pxoff + k) = ((*(dst + pxoff + k) + *(dst + pxoff - p + k)) >> 1) & 0x7f;
+                *(dst + pxoff + 1 * p) = (*(dst + pxoff + 1 * p) + *(dst + pxoff + 1 * p - 1)) >> 1;
+                *(dst + pxoff + 2 * p) = (*(dst + pxoff + 2 * p) + *(dst + pxoff + 2 * p - 1)) >> 1;
+                *(dst + pxoff + 3 * p) = (*(dst + pxoff + 3 * p) + *(dst + pxoff + 3 * p - 1)) >> 1;
+            }
+        }
+    }
+    return 0;
+}
+
 static int rle_decode(SANMVideoContext *ctx, uint8_t *dst, const int out_size)
 {
     int opcode, color, run_len, left = out_size;
@@ -1436,6 +1660,11 @@ static int process_frame_obj(SANMVideoContext *ctx)
         return old_codec1(ctx, top, left, w, h, codec == 3);
     case 2:
         return old_codec2(ctx, top, left, w, h);
+    case 4:
+    case 5:
+    case 33:
+    case 34:
+        return old_codec4(ctx, top, left, w, h, param, parm2, codec);
     case 21:
         return old_codec21(ctx, top, left, w, h);
     case 23:
-- 
2.48.1

_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".

^ permalink raw reply	[flat|nested] 15+ messages in thread

* [FFmpeg-devel] [PATCH v2 10/13] avcodec/sanm: codec37: reimplement comp4
  2025-03-15  9:32 [FFmpeg-devel] [PATCH v2 00/13] avcodec/sanm: various improvements Manuel Lauss
                   ` (8 preceding siblings ...)
  2025-03-15  9:32 ` [FFmpeg-devel] [PATCH v2 09/13] avcodec/sanm: codec4/5/33/34 decoder Manuel Lauss
@ 2025-03-15  9:32 ` Manuel Lauss
  2025-03-15  9:32 ` [FFmpeg-devel] [PATCH v2 11/13] avcodec/sanm: implement STOR/FTCH for ANIMv1 Manuel Lauss
                   ` (2 subsequent siblings)
  12 siblings, 0 replies; 15+ messages in thread
From: Manuel Lauss @ 2025-03-15  9:32 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: Manuel Lauss

Compression 4 code 0 means copy from delta buffer without mv,
AND start of a skip run.  This gets rid of the extra case and column
index manipulation and implements this as it is implemented in the
original game exe, i.e. as a special case for after mv copy.

Signed-off-by: Manuel Lauss <manuel.lauss@gmail.com>
---
 libavcodec/sanm.c | 8 +++-----
 1 file changed, 3 insertions(+), 5 deletions(-)

diff --git a/libavcodec/sanm.c b/libavcodec/sanm.c
index eecfefd43b..2d7a0eeb32 100644
--- a/libavcodec/sanm.c
+++ b/libavcodec/sanm.c
@@ -1106,16 +1106,14 @@ static int old_codec37(SANMVideoContext *ctx, int width, int height)
                     t = bytestream2_get_byteu(&ctx->gb);
                     for (k = 0; k < 4; k++)
                         memset(dst + i + k * stride, t, 4);
-               } else if ((compr == 4) && (code == 0)) {
-                    if (bytestream2_get_bytes_left(&ctx->gb) < 1)
-                        return AVERROR_INVALIDDATA;
-                    skip_run = bytestream2_get_byteu(&ctx->gb) + 1;
-                    i -= 4;
                } else {
                     mx = c37_mv[(mvoff * 255 + code) * 2];
                     my = c37_mv[(mvoff * 255 + code) * 2 + 1];
                     codec37_mv(dst + i, prev + i + mx + my * stride,
                                ctx->height, stride, i + mx, j + my);
+
+                    if ((compr == 4) && (code == 0))
+                        skip_run = bytestream2_get_byteu(&ctx->gb);
                 }
             }
             dst  += stride * 4;
-- 
2.48.1

_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".

^ permalink raw reply	[flat|nested] 15+ messages in thread

* [FFmpeg-devel] [PATCH v2 11/13] avcodec/sanm: implement STOR/FTCH for ANIMv1
  2025-03-15  9:32 [FFmpeg-devel] [PATCH v2 00/13] avcodec/sanm: various improvements Manuel Lauss
                   ` (9 preceding siblings ...)
  2025-03-15  9:32 ` [FFmpeg-devel] [PATCH v2 10/13] avcodec/sanm: codec37: reimplement comp4 Manuel Lauss
@ 2025-03-15  9:32 ` Manuel Lauss
  2025-03-15  9:32 ` [FFmpeg-devel] [PATCH v2 12/13] avcodec/sanm: ignore codec45 Manuel Lauss
  2025-03-15  9:32 ` [FFmpeg-devel] [PATCH v2 13/13] MAINTAINERS: add Manuel Lauss as sanm.c/smush.c Maintainer Manuel Lauss
  12 siblings, 0 replies; 15+ messages in thread
From: Manuel Lauss @ 2025-03-15  9:32 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: Manuel Lauss

Handle STOR/FTCH the same way the RA1 game engine does:
On STOR, save the next following FOBJ (not the decoded image)
in a buffer; decode it on FTCH.
The RA1 codecs and the fobj handler now take an explicit
GetByteContext in order to be able to replay stored data.

Used extensively by Rebel Assault 1 for e.g. backgrounds and
the cockpit overlay.

For ANIMv2 keep the current system to store the decoded image, as
replaying a FOBJ would not work with codecs37/47/48 due to sequence
violations.

Signed-off-by: Manuel Lauss <manuel.lauss@gmail.com>
---
v2: reimplemented by giving the RA1 codecs an explicit GetByteContext*
    to support the replay of data from different memory.

 libavcodec/sanm.c | 226 +++++++++++++++++++++++++++++++---------------
 1 file changed, 154 insertions(+), 72 deletions(-)

diff --git a/libavcodec/sanm.c b/libavcodec/sanm.c
index 2d7a0eeb32..b86f67c3bf 100644
--- a/libavcodec/sanm.c
+++ b/libavcodec/sanm.c
@@ -598,7 +598,8 @@ static void codec4_gen_tiles(SANMVideoContext *ctx, uint16_t param1)
 }
 
 
-static int codec4_load_tiles(SANMVideoContext *ctx, uint16_t param2, uint8_t clr)
+static int codec4_load_tiles(SANMVideoContext *ctx, GetByteContext *gb,
+			     uint16_t param2, uint8_t clr)
 {
     uint8_t c, *dst = (uint8_t *)&(ctx->c4tbl[1][0][0]);
     uint32_t loop = param2 * 8;
@@ -606,11 +607,11 @@ static int codec4_load_tiles(SANMVideoContext *ctx, uint16_t param2, uint8_t clr
     if (param2 > 256)
         return AVERROR_INVALIDDATA;
 
-    if (bytestream2_get_bytes_left(&ctx->gb) < loop)
+    if (bytestream2_get_bytes_left(gb) < loop)
         return AVERROR_INVALIDDATA;
 
     while (loop--) {
-        c = bytestream2_get_byteu(&ctx->gb);
+        c = bytestream2_get_byteu(gb);
         *dst++ = (c >> 4) + clr;
         *dst++ = (c & 0xf) + clr;
     }
@@ -671,8 +672,8 @@ static av_cold int decode_end(AVCodecContext *avctx)
     return 0;
 }
 
-static int old_codec4(SANMVideoContext *ctx, int top, int left, int w, int h,
-                      uint8_t param, uint16_t param2, int codec)
+static int old_codec4(SANMVideoContext *ctx, GetByteContext *gb, int top, int left,
+		      int w, int h, uint8_t param, uint16_t param2, int codec)
 {
     const uint16_t p = ctx->pitch;
     const uint32_t maxpxo = ctx->height * p;
@@ -688,7 +689,7 @@ static int old_codec4(SANMVideoContext *ctx, int top, int left, int w, int h,
         ctx->c4param = param;
     }
     if (param2 > 0) {
-        ret = codec4_load_tiles(ctx, param2, param);
+        ret = codec4_load_tiles(ctx, gb, param2, param);
         if (ret)
             return ret;
     }
@@ -702,9 +703,9 @@ static int old_codec4(SANMVideoContext *ctx, int top, int left, int w, int h,
             pxoff = j + left + ((top + i) * p);
             if (param2 > 0) {
                 if (bits == 0) {
-                    if (bytestream2_get_bytes_left(&ctx->gb) < 1)
+                    if (bytestream2_get_bytes_left(gb) < 1)
                         return AVERROR_INVALIDDATA;
-                    mask = bytestream2_get_byteu(&ctx->gb);
+                    mask = bytestream2_get_byteu(gb);
                     bits = 8;
                 }
                 bit = !!(mask & 0x80);
@@ -714,9 +715,9 @@ static int old_codec4(SANMVideoContext *ctx, int top, int left, int w, int h,
                 bit = 0;
             }
 
-            if (bytestream2_get_bytes_left(&ctx->gb) < 1)
+            if (bytestream2_get_bytes_left(gb) < 1)
                 return AVERROR_INVALIDDATA;
-            idx = bytestream2_get_byteu(&ctx->gb);
+            idx = bytestream2_get_byteu(gb);
             if ((bit == 0) && (idx == 0x80) && (codec != 5))
                 continue;
 
@@ -756,23 +757,23 @@ static int old_codec4(SANMVideoContext *ctx, int top, int left, int w, int h,
     return 0;
 }
 
-static int rle_decode(SANMVideoContext *ctx, uint8_t *dst, const int out_size)
+static int rle_decode(SANMVideoContext *ctx, GetByteContext *gb, uint8_t *dst, const int out_size)
 {
     int opcode, color, run_len, left = out_size;
 
     while (left > 0) {
-        opcode = bytestream2_get_byte(&ctx->gb);
+        opcode = bytestream2_get_byte(gb);
         run_len = (opcode >> 1) + 1;
-        if (run_len > left || bytestream2_get_bytes_left(&ctx->gb) <= 0)
+        if (run_len > left || bytestream2_get_bytes_left(gb) <= 0)
             return AVERROR_INVALIDDATA;
 
         if (opcode & 1) {
-            color = bytestream2_get_byte(&ctx->gb);
+            color = bytestream2_get_byte(gb);
             memset(dst, color, run_len);
         } else {
-            if (bytestream2_get_bytes_left(&ctx->gb) < run_len)
+            if (bytestream2_get_bytes_left(gb) < run_len)
                 return AVERROR_INVALIDDATA;
-            bytestream2_get_bufferu(&ctx->gb, dst, run_len);
+            bytestream2_get_bufferu(gb, dst, run_len);
         }
 
         dst  += run_len;
@@ -782,8 +783,8 @@ static int rle_decode(SANMVideoContext *ctx, uint8_t *dst, const int out_size)
     return 0;
 }
 
-static int old_codec23(SANMVideoContext *ctx, int top, int left, int width,
-                       int height, uint8_t param, uint16_t param2)
+static int old_codec23(SANMVideoContext *ctx, GetByteContext *gb, int top, int left,
+		       int width, int height, uint8_t param, uint16_t param2)
 {
     const uint32_t maxpxo = ctx->height * ctx->pitch;
     uint8_t *dst, lut[256], c;
@@ -795,30 +796,30 @@ static int old_codec23(SANMVideoContext *ctx, int top, int left, int width,
         for (i = 0; i < 256; i++)
             lut[i] = (i + param + 0xd0) & 0xff;
     } else if (param2 == 256) {
-        if (bytestream2_get_bytes_left(&ctx->gb) < 256)
+        if (bytestream2_get_bytes_left(gb) < 256)
             return AVERROR_INVALIDDATA;
-        bytestream2_get_bufferu(&ctx->gb, ctx->c23lut, 256);
+        bytestream2_get_bufferu(gb, ctx->c23lut, 256);
     } else if (param2 < 256) {
         for (i = 0; i < 256; i++)
             lut[i] = (i + param2) & 0xff;
     } else {
         memcpy(lut, ctx->c23lut, 256);
     }
-    if (bytestream2_get_bytes_left(&ctx->gb) < 1)
+    if (bytestream2_get_bytes_left(gb) < 1)
         return 0;  /* some c23 frames just set up the LUT */
 
     dst = (uint8_t *)ctx->frm0;
     for (i = 0; i < height; i++) {
-        if (bytestream2_get_bytes_left(&ctx->gb) < 2)
+        if (bytestream2_get_bytes_left(gb) < 2)
             return 0;
         pxoff = left + ((top + i) * ctx->pitch);
-        k = bytestream2_get_le16u(&ctx->gb);
+        k = bytestream2_get_le16u(gb);
         sk = 1;
         pc = 0;
         while (k > 0 && pc <= width) {
-            if (bytestream2_get_bytes_left(&ctx->gb) < 1)
+            if (bytestream2_get_bytes_left(gb) < 1)
                 return AVERROR_INVALIDDATA;
-            j = bytestream2_get_byteu(&ctx->gb);
+            j = bytestream2_get_byteu(gb);
             if (sk) {
                 pxoff += j;
                 pc += j;
@@ -838,8 +839,8 @@ static int old_codec23(SANMVideoContext *ctx, int top, int left, int width,
     return 0;
 }
 
-static int old_codec21(SANMVideoContext *ctx, int top, int left, int width,
-                       int height)
+static int old_codec21(SANMVideoContext *ctx, GetByteContext *gb, int top, int left,
+		       int width, int height)
 {
     const uint32_t maxpxo = ctx->height * ctx->pitch;
     uint8_t *dst = (uint8_t *)ctx->frm0, c;
@@ -847,25 +848,25 @@ static int old_codec21(SANMVideoContext *ctx, int top, int left, int width,
 
     dst = (uint8_t *)ctx->frm0;
     for (i = 0; i < height; i++) {
-        if (bytestream2_get_bytes_left(&ctx->gb) < 2)
+        if (bytestream2_get_bytes_left(gb) < 2)
             return 0;
         pxoff = left + ((top + i) * ctx->pitch);
-        k = bytestream2_get_le16u(&ctx->gb);
+        k = bytestream2_get_le16u(gb);
         sk = 1;
         pc = 0;
         while (k > 0 && pc <= width) {
-            if (bytestream2_get_bytes_left(&ctx->gb) < 2)
+            if (bytestream2_get_bytes_left(gb) < 2)
                 return AVERROR_INVALIDDATA;
-            j = bytestream2_get_le16u(&ctx->gb);
+            j = bytestream2_get_le16u(gb);
             k -= 2;
             if (sk) {
                 pxoff += j;
                 pc += j;
             } else {
-                if (bytestream2_get_bytes_left(&ctx->gb) < (j + 1))
+                if (bytestream2_get_bytes_left(gb) < (j + 1))
                     return AVERROR_INVALIDDATA;
                 do {
-                    c = bytestream2_get_byteu(&ctx->gb);
+                    c = bytestream2_get_byteu(gb);
                     if (pxoff >=0 && pxoff < maxpxo) {
                         *(dst + pxoff) = c;
                     }
@@ -881,7 +882,7 @@ static int old_codec21(SANMVideoContext *ctx, int top, int left, int width,
     return 0;
 }
 
-static int old_codec1(SANMVideoContext *ctx, int top,
+static int old_codec1(SANMVideoContext *ctx, GetByteContext *gb, int top,
                       int left, int width, int height, int opaque)
 {
     int i, j, len, flag, code, val, end, pxoff;
@@ -889,22 +890,22 @@ static int old_codec1(SANMVideoContext *ctx, int top,
     uint8_t *dst = (uint8_t *)ctx->frm0;
 
     for (i = 0; i < height; i++) {
-        if (bytestream2_get_bytes_left(&ctx->gb) < 2)
+        if (bytestream2_get_bytes_left(gb) < 2)
             return AVERROR_INVALIDDATA;
 
-        len = bytestream2_get_le16u(&ctx->gb);
-        end = bytestream2_tell(&ctx->gb) + len;
+        len = bytestream2_get_le16u(gb);
+        end = bytestream2_tell(gb) + len;
 
         pxoff = left + ((top + i) * ctx->pitch);
-        while (bytestream2_tell(&ctx->gb) < end) {
-            if (bytestream2_get_bytes_left(&ctx->gb) < 2)
+        while (bytestream2_tell(gb) < end) {
+            if (bytestream2_get_bytes_left(gb) < 2)
                 return AVERROR_INVALIDDATA;
 
-            code = bytestream2_get_byteu(&ctx->gb);
+            code = bytestream2_get_byteu(gb);
             flag = code & 1;
             code = (code >> 1) + 1;
             if (flag) {
-                val = bytestream2_get_byteu(&ctx->gb);
+                val = bytestream2_get_byteu(gb);
                 if (val || opaque) {
                     for (j = 0; j < code; j++) {
                         if (pxoff >= 0 && pxoff < maxpxo)
@@ -915,10 +916,10 @@ static int old_codec1(SANMVideoContext *ctx, int top,
                     pxoff += code;
                 }
             } else {
-                if (bytestream2_get_bytes_left(&ctx->gb) < code)
+                if (bytestream2_get_bytes_left(gb) < code)
                     return AVERROR_INVALIDDATA;
                 for (j = 0; j < code; j++) {
-                    val = bytestream2_get_byteu(&ctx->gb);
+                    val = bytestream2_get_byteu(gb);
                     if ((pxoff >= 0) && (pxoff < maxpxo) && (val || opaque))
                         *(dst + pxoff) = val;
                     pxoff++;
@@ -931,16 +932,16 @@ static int old_codec1(SANMVideoContext *ctx, int top,
     return 0;
 }
 
-static int old_codec2(SANMVideoContext *ctx, int top,
+static int old_codec2(SANMVideoContext *ctx, GetByteContext *gb, int top,
                       int left, int width, int height)
 {
     uint8_t *dst = (uint8_t *)ctx->frm0, col;
     int16_t xpos = left, ypos = top;
 
-    while (bytestream2_get_bytes_left(&ctx->gb) > 3) {
-        xpos += bytestream2_get_le16u(&ctx->gb);
-        ypos += bytestream2_get_byteu(&ctx->gb);
-        col = bytestream2_get_byteu(&ctx->gb);
+    while (bytestream2_get_bytes_left(gb) > 3) {
+        xpos += bytestream2_get_le16u(gb);
+        ypos += bytestream2_get_byteu(gb);
+        col = bytestream2_get_byteu(gb);
         if (xpos >= 0 && ypos >= 0 &&
             xpos < ctx->width && ypos < ctx->height) {
                 *(dst + xpos + ypos * ctx->pitch) = col;
@@ -1073,7 +1074,7 @@ static int old_codec37(SANMVideoContext *ctx, int width, int height)
         }
         break;
     case 2:
-        if (rle_decode(ctx, dst, decoded_size))
+        if (rle_decode(ctx, &ctx->gb, dst, decoded_size))
             return AVERROR_INVALIDDATA;
         memset(ctx->frm2, 0, ctx->frm2_size);
         break;
@@ -1340,7 +1341,7 @@ static int old_codec47(SANMVideoContext *ctx, int width, int height)
         memcpy(ctx->frm0, ctx->frm1, ctx->pitch * ctx->height);
         break;
     case 5:
-        if (rle_decode(ctx, dst, decoded_size))
+        if (rle_decode(ctx, &ctx->gb, dst, decoded_size))
             return AVERROR_INVALIDDATA;
         break;
     default:
@@ -1547,7 +1548,7 @@ static int old_codec48(SANMVideoContext *ctx, int width, int height)
         }
         break;
     case 2:
-        if (rle_decode(ctx, dst, decoded_size))
+        if (rle_decode(ctx, &ctx->gb, dst, decoded_size))
             return AVERROR_INVALIDDATA;
         break;
     case 3:
@@ -1578,17 +1579,23 @@ static int old_codec48(SANMVideoContext *ctx, int width, int height)
     return 0;
 }
 
-static int process_frame_obj(SANMVideoContext *ctx)
+static int process_frame_obj(SANMVideoContext *ctx, GetByteContext *gb)
 {
-    uint16_t parm2;
-    uint8_t  codec = bytestream2_get_byteu(&ctx->gb);
-    uint8_t  param = bytestream2_get_byteu(&ctx->gb);
-    int16_t  left  = bytestream2_get_le16u(&ctx->gb);
-    int16_t  top   = bytestream2_get_le16u(&ctx->gb);
-    uint16_t w     = bytestream2_get_le16u(&ctx->gb);
-    uint16_t h     = bytestream2_get_le16u(&ctx->gb);
-    bytestream2_skip(&ctx->gb, 2);
-    parm2 = bytestream2_get_le16u(&ctx->gb);
+    uint16_t w, h, parm2;
+    uint8_t codec, param;
+    int16_t left, top;
+
+    if (gb == NULL)
+        gb = &ctx->gb;
+
+    codec = bytestream2_get_byteu(gb);
+    param = bytestream2_get_byteu(gb);
+    left  = bytestream2_get_le16u(gb);
+    top   = bytestream2_get_le16u(gb);
+    w     = bytestream2_get_le16u(gb);
+    h     = bytestream2_get_le16u(gb);
+    bytestream2_skip(gb, 2);
+    parm2 = bytestream2_get_le16u(gb);
 
     if (w < 1 || h < 1 || w > 800 || h > 600 || left > 800 || top > 600) {
         av_log(ctx->avctx, AV_LOG_WARNING,
@@ -1655,18 +1662,18 @@ static int process_frame_obj(SANMVideoContext *ctx)
     switch (codec) {
     case 1:
     case 3:
-        return old_codec1(ctx, top, left, w, h, codec == 3);
+        return old_codec1(ctx, gb, top, left, w, h, codec == 3);
     case 2:
-        return old_codec2(ctx, top, left, w, h);
+        return old_codec2(ctx, gb, top, left, w, h);
     case 4:
     case 5:
     case 33:
     case 34:
-        return old_codec4(ctx, top, left, w, h, param, parm2, codec);
+        return old_codec4(ctx, gb, top, left, w, h, param, parm2, codec);
     case 21:
-        return old_codec21(ctx, top, left, w, h);
+        return old_codec21(ctx, gb, top, left, w, h);
     case 23:
-        return old_codec23(ctx, top, left, w, h, param, parm2);
+        return old_codec23(ctx, gb, top, left, w, h, param, parm2);
     case 37:
         return old_codec37(ctx, w, h);
     case 47:
@@ -1680,6 +1687,55 @@ static int process_frame_obj(SANMVideoContext *ctx)
     }
 }
 
+static int process_ftch(SANMVideoContext *ctx, int size)
+{
+    uint8_t *sf = ctx->stored_frame;
+    int xoff, yoff, left, top, ret;
+    GetByteContext gb;
+    uint32_t sz;
+
+    /* FTCH defines additional x/y offsets */
+    if (size != 12) {
+        if (bytestream2_get_bytes_left(&ctx->gb) < 6)
+            return AVERROR_INVALIDDATA;
+        bytestream2_skip(&ctx->gb, 2);
+        xoff = bytestream2_get_le16u(&ctx->gb);
+        yoff = bytestream2_get_le16u(&ctx->gb);
+    } else {
+        if (bytestream2_get_bytes_left(&ctx->gb) < 12)
+            return AVERROR_INVALIDDATA;
+        bytestream2_skip(&ctx->gb, 4);
+        xoff = bytestream2_get_be32u(&ctx->gb);
+        yoff = bytestream2_get_be32u(&ctx->gb);
+    }
+
+    sz = *(uint32_t *)(sf + 0);
+    if ((sz > 0) && (sz <= ctx->stored_frame_size - 4)) {
+        /* add the FTCH offsets to the left/top values of the stored FOBJ */
+        left = av_le2ne16(*(int16_t *)(sf + 4 + 2));
+        top  = av_le2ne16(*(int16_t *)(sf + 4 + 4));
+        *(int16_t *)(sf + 4 + 2) = av_le2ne16(left + xoff);
+        *(int16_t *)(sf + 4 + 4) = av_le2ne16(top  + yoff);
+
+        /* decode the stored FOBJ */
+        bytestream2_init(&gb, sf + 4, sz);
+        ret = process_frame_obj(ctx, &gb);
+
+        /* now restore the original left/top values again */
+        *(int16_t *)(sf + 4 + 2) = av_le2ne16(left);
+        *(int16_t *)(sf + 4 + 4) = av_le2ne16(top);
+    } else {
+        /* this happens a lot in RA1: The individual files are meant to
+         * be played in sequence, with some referencing objects STORed
+         * by previous files, e.g. the cockpit codec21 object in RA1 LVL8.
+         * But spamming the log with errors is also not helpful, so
+         * here we simply ignore this case.
+         */
+         ret = 0;
+    }
+    return ret;
+}
+
 static int process_xpal(SANMVideoContext *ctx, int size)
 {
     int16_t *dp = ctx->delta_pal;
@@ -1990,7 +2046,7 @@ static int decode_5(SANMVideoContext *ctx)
 #endif
     uint8_t *dst = (uint8_t*)ctx->frm0;
 
-    if (rle_decode(ctx, dst, ctx->buf_size))
+    if (rle_decode(ctx, &ctx->gb, dst, ctx->buf_size))
         return AVERROR_INVALIDDATA;
 
 #if HAVE_BIGENDIAN
@@ -2033,7 +2089,7 @@ static int decode_8(SANMVideoContext *ctx)
     }
     rsrc = ctx->rle_buf;
 
-    if (rle_decode(ctx, rsrc, npixels))
+    if (rle_decode(ctx, &ctx->gb, rsrc, npixels))
         return AVERROR_INVALIDDATA;
 
     while (npixels--)
@@ -2160,9 +2216,32 @@ static int decode_frame(AVCodecContext *avctx, AVFrame *frame,
             case MKBETAG('F', 'O', 'B', 'J'):
                 if (size < 16)
                     return AVERROR_INVALIDDATA;
-                if (ret = process_frame_obj(ctx))
+                if (ret = process_frame_obj(ctx, NULL))
                     return ret;
                 have_img = 1;
+
+                /* STOR: for ANIMv0/1 store the whole FOBJ datablock, as it
+                 * needs to be replayed on FTCH, since none of the codecs
+                 * it uses work on the full buffer.
+                 * For ANIMv2, it's enough to store the current framebuffer.
+                 */
+                if (to_store) {
+                    to_store = 0;
+                    if (ctx->subversion < 2) {
+                        if (size + 4 <= ctx->stored_frame_size) {
+                            int pos2 = bytestream2_tell(&ctx->gb);
+                            bytestream2_seek(&ctx->gb, pos, SEEK_SET);
+                            *(uint32_t *)(ctx->stored_frame) = size;
+                            bytestream2_get_bufferu(&ctx->gb, ctx->stored_frame + 4, size);
+                            bytestream2_seek(&ctx->gb, pos2, SEEK_SET);
+                        } else {
+                            av_log(avctx, AV_LOG_ERROR, "FOBJ too large for STOR\n");
+                            ret = AVERROR(ENOMEM);
+                        }
+                    } else {
+                        memcpy(ctx->stored_frame, ctx->frm0, ctx->buf_size);
+                    }
+                }
                 break;
             case MKBETAG('X', 'P', 'A', 'L'):
                 if (ret = process_xpal(ctx, size))
@@ -2172,7 +2251,12 @@ static int decode_frame(AVCodecContext *avctx, AVFrame *frame,
                 to_store = 1;
                 break;
             case MKBETAG('F', 'T', 'C', 'H'):
-                memcpy(ctx->frm0, ctx->stored_frame, ctx->buf_size);
+                if (ctx->subversion < 2) {
+                    if (ret = process_ftch(ctx, size))
+                        return ret;
+                } else {
+                    memcpy(ctx->frm0, ctx->stored_frame, ctx->buf_size);
+                }
                 have_img = 1;
                 break;
             default:
@@ -2193,8 +2277,6 @@ static int decode_frame(AVCodecContext *avctx, AVFrame *frame,
                     bytestream2_seek(&ctx->gb, pos + size, SEEK_SET);
             }
         }
-        if (to_store)
-            memcpy(ctx->stored_frame, ctx->frm0, ctx->buf_size);
 
         if (have_img && ctx->have_dimensions) {
             if ((ret = copy_output(ctx, NULL)))
-- 
2.48.1

_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".

^ permalink raw reply	[flat|nested] 15+ messages in thread

* [FFmpeg-devel] [PATCH v2 12/13] avcodec/sanm: ignore codec45
  2025-03-15  9:32 [FFmpeg-devel] [PATCH v2 00/13] avcodec/sanm: various improvements Manuel Lauss
                   ` (10 preceding siblings ...)
  2025-03-15  9:32 ` [FFmpeg-devel] [PATCH v2 11/13] avcodec/sanm: implement STOR/FTCH for ANIMv1 Manuel Lauss
@ 2025-03-15  9:32 ` Manuel Lauss
  2025-03-15  9:32 ` [FFmpeg-devel] [PATCH v2 13/13] MAINTAINERS: add Manuel Lauss as sanm.c/smush.c Maintainer Manuel Lauss
  12 siblings, 0 replies; 15+ messages in thread
From: Manuel Lauss @ 2025-03-15  9:32 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: Manuel Lauss

Codec45 is used in some RA2 videos on top of codec37.

It consists of 2 tables (768 and 32768 bytes), and datapackets
like codec2 (delta-x, delta-y) with a pixel counter instead of a color value.
It then reads the 4 surrounding pixels, looks up 3 separate values
for each in table1, adds them together to form an index into
table2 for a new pixel value, in a row.

The data coming in gets the x/y coordinates out of the visible
area very quickly (2-3 iterations); I don't see any visual
difference between the ffmpeg-produced frames and ones captured
with dosbox from rebel assault 2, which leads me to believe this
codec never worked as intended.

Signed-off-by: Manuel Lauss <manuel.lauss@gmail.com>
---
v2: newly added.

 libavcodec/sanm.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/libavcodec/sanm.c b/libavcodec/sanm.c
index b86f67c3bf..7e39c5eeb0 100644
--- a/libavcodec/sanm.c
+++ b/libavcodec/sanm.c
@@ -1676,6 +1676,8 @@ static int process_frame_obj(SANMVideoContext *ctx, GetByteContext *gb)
         return old_codec23(ctx, gb, top, left, w, h, param, parm2);
     case 37:
         return old_codec37(ctx, w, h);
+    case 45:
+        return 0;
     case 47:
         return old_codec47(ctx, w, h);
     case 48:
-- 
2.48.1

_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".

^ permalink raw reply	[flat|nested] 15+ messages in thread

* [FFmpeg-devel] [PATCH v2 13/13] MAINTAINERS: add Manuel Lauss as sanm.c/smush.c Maintainer
  2025-03-15  9:32 [FFmpeg-devel] [PATCH v2 00/13] avcodec/sanm: various improvements Manuel Lauss
                   ` (11 preceding siblings ...)
  2025-03-15  9:32 ` [FFmpeg-devel] [PATCH v2 12/13] avcodec/sanm: ignore codec45 Manuel Lauss
@ 2025-03-15  9:32 ` Manuel Lauss
  2025-03-16 14:27   ` Michael Niedermayer
  12 siblings, 1 reply; 15+ messages in thread
From: Manuel Lauss @ 2025-03-15  9:32 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: Manuel Lauss

As requested by Michael Niedermayer.

Signed-off-by: Manuel Lauss <manuel.lauss@gmail.com>
---
v2: newly added.

 MAINTAINERS | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 9714581c6b..917eca458e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -244,6 +244,7 @@ Codecs:
   rpza.c                                Roberto Togni
   rtjpeg.c, rtjpeg.h                    Reimar Doeffinger
   rv10.c                                Michael Niedermayer
+  sanm.c                                Manuel Lauss
   smc.c                                 Mike Melanson
   snow*                                 Michael Niedermayer, Loren Merritt
   sonic.c                               Alex Beregszaszi
@@ -465,6 +466,7 @@ Muxers/Demuxers:
   sdp.c                                 Martin Storsjo
   segafilm.c                            Mike Melanson
   segment.c                             Stefano Sabatini
+  smush.c                               Manuel Lauss
   spdif*                                Anssi Hannula
   srtdec.c                              Aurelien Jacobs
   swf.c                                 Baptiste Coudurier
-- 
2.48.1

_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [FFmpeg-devel] [PATCH v2 13/13] MAINTAINERS: add Manuel Lauss as sanm.c/smush.c Maintainer
  2025-03-15  9:32 ` [FFmpeg-devel] [PATCH v2 13/13] MAINTAINERS: add Manuel Lauss as sanm.c/smush.c Maintainer Manuel Lauss
@ 2025-03-16 14:27   ` Michael Niedermayer
  0 siblings, 0 replies; 15+ messages in thread
From: Michael Niedermayer @ 2025-03-16 14:27 UTC (permalink / raw)
  To: FFmpeg development discussions and patches


[-- Attachment #1.1: Type: text/plain, Size: 475 bytes --]

Hi Manuel

On Sat, Mar 15, 2025 at 10:32:20AM +0100, Manuel Lauss wrote:
> As requested by Michael Niedermayer.
> 
> Signed-off-by: Manuel Lauss <manuel.lauss@gmail.com>
> ---
> v2: newly added.
> 
>  MAINTAINERS | 2 ++
>  1 file changed, 2 insertions(+)

will apply in a day or so

thx

[...]
-- 
Michael     GnuPG fingerprint: 9FF2128B147EF6730BADF133611EC787040B0FAB

The misfortune of the wise is better than the prosperity of the fool.
-- Epicurus

[-- Attachment #1.2: signature.asc --]
[-- Type: application/pgp-signature, Size: 195 bytes --]

[-- Attachment #2: Type: text/plain, Size: 251 bytes --]

_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".

^ permalink raw reply	[flat|nested] 15+ messages in thread

end of thread, other threads:[~2025-03-16 14:27 UTC | newest]

Thread overview: 15+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-03-15  9:32 [FFmpeg-devel] [PATCH v2 00/13] avcodec/sanm: various improvements Manuel Lauss
2025-03-15  9:32 ` [FFmpeg-devel] [PATCH v2 01/13] avcodec/sanm: disable left/top for fullscreen codecs Manuel Lauss
2025-03-15  9:32 ` [FFmpeg-devel] [PATCH v2 02/13] avcodec/sanm: FOBJ left/top are signed values Manuel Lauss
2025-03-15  9:32 ` [FFmpeg-devel] [PATCH v2 03/13] avcodec/sanm: better frame size detection for old codecs Manuel Lauss
2025-03-15  9:32 ` [FFmpeg-devel] [PATCH v2 04/13] avcodec/sanm: better ANIMv1 engine support Manuel Lauss
2025-03-15  9:32 ` [FFmpeg-devel] [PATCH v2 05/13] avcodec/sanm: fix codec3 Manuel Lauss
2025-03-15  9:32 ` [FFmpeg-devel] [PATCH v2 06/13] avcodec/sanm: codec2 decoder Manuel Lauss
2025-03-15  9:32 ` [FFmpeg-devel] [PATCH v2 07/13] avcodec/sanm: codec23 decoder Manuel Lauss
2025-03-15  9:32 ` [FFmpeg-devel] [PATCH v2 08/13] avcodec/sanm: codec21 decoder Manuel Lauss
2025-03-15  9:32 ` [FFmpeg-devel] [PATCH v2 09/13] avcodec/sanm: codec4/5/33/34 decoder Manuel Lauss
2025-03-15  9:32 ` [FFmpeg-devel] [PATCH v2 10/13] avcodec/sanm: codec37: reimplement comp4 Manuel Lauss
2025-03-15  9:32 ` [FFmpeg-devel] [PATCH v2 11/13] avcodec/sanm: implement STOR/FTCH for ANIMv1 Manuel Lauss
2025-03-15  9:32 ` [FFmpeg-devel] [PATCH v2 12/13] avcodec/sanm: ignore codec45 Manuel Lauss
2025-03-15  9:32 ` [FFmpeg-devel] [PATCH v2 13/13] MAINTAINERS: add Manuel Lauss as sanm.c/smush.c Maintainer Manuel Lauss
2025-03-16 14:27   ` Michael Niedermayer

Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

This inbox may be cloned and mirrored by anyone:

	git clone --mirror https://master.gitmailbox.com/ffmpegdev/0 ffmpegdev/git/0.git

	# If you have public-inbox 1.1+ installed, you may
	# initialize and index your mirror using the following commands:
	public-inbox-init -V2 ffmpegdev ffmpegdev/ https://master.gitmailbox.com/ffmpegdev \
		ffmpegdev@gitmailbox.com
	public-inbox-index ffmpegdev

Example config snippet for mirrors.


AGPL code for this site: git clone https://public-inbox.org/public-inbox.git