[FFmpeg-devel] [PATCH 12/12] avcodec/sanm: properly implement STOR/FTCH for ANIMv1
Manuel Lauss
manuel.lauss at gmail.com
Thu Mar 13 13:15:05 EET 2025
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.
Used extensively by Rebel Assault 1 for e.g. the cockpit: A STOR-ed
codec21 object which only covers parts of the buffer is FTCHed
as last object in the stack.
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 at gmail.com>
---
libavcodec/sanm.c | 84 ++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 80 insertions(+), 4 deletions(-)
diff --git a/libavcodec/sanm.c b/libavcodec/sanm.c
index 207db4a8fb..da7fb4a9e0 100644
--- a/libavcodec/sanm.c
+++ b/libavcodec/sanm.c
@@ -1671,6 +1671,57 @@ static int process_frame_obj(SANMVideoContext *ctx)
}
}
+static int process_ftch(SANMVideoContext *ctx, int size)
+{
+ GetByteContext gb, *old_gb = ctx->gb;
+ uint8_t *sf = ctx->stored_frame;
+ int xoff, yoff, left, top, ret;
+ 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);
+ ctx->gb = &gb;
+ ret = process_frame_obj(ctx);
+ ctx->gb = old_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;
@@ -2158,6 +2209,29 @@ static int decode_frame(AVCodecContext *avctx, AVFrame *frame,
if (ret = process_frame_obj(ctx))
return ret;
have_pic = 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))
@@ -2167,8 +2241,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);
- have_pic = 1;
+ if (ctx->subversion < 2) {
+ if (ret = process_ftch(ctx, size))
+ return ret;
+ } else {
+ memcpy(ctx->frm0, ctx->stored_frame, ctx->buf_size);
+ }
break;
default:
bytestream2_skip(ctx->gb, size);
@@ -2181,8 +2259,6 @@ static int decode_frame(AVCodecContext *avctx, AVFrame *frame,
if (size & 1)
bytestream2_skip(ctx->gb, 1);
}
- if (to_store)
- memcpy(ctx->stored_frame, ctx->frm0, ctx->buf_size);
if (have_pic && ctx->have_dimensions) {
if ((ret = copy_output(ctx, NULL)))
--
2.48.1
More information about the ffmpeg-devel
mailing list