static int decode_bmv_frame(const uint8_t *source, int src_len, uint8_t *frame, int frame_off) { int val, saved_val = 0; int tmplen = src_len; const uint8_t *src, *source_end = source + src_len; uint8_t *frame_end = frame + SCREEN_WIDE * SCREEN_HIGH; uint8_t *dst, *dst_end; int len, mask; int forward = (frame_off <= -SCREEN_WIDE) || (frame_off >= 0); int read_two_nibbles, flag; int advance_mode; int mode = 0; int i; if (src_len <= 0) return -1; if (forward) { src = source; dst = frame; dst_end = frame_end; } else { src = source + src_len - 1; dst = frame_end - 1; dst_end = frame - 1; } for (;;) { int shift = 0; flag = 0; /* The mode/len decoding is a bit strange: * values are coded as variable-length codes with nibble units, * code end is signalled by two top bits in the nibble being nonzero. * And since data is bytepacked and we read two nibbles at a time, * we may get a nibble belonging to the next code. * Hence this convoluted loop. */ if (!mode || (tmplen == 4)) { if (src < source || src >= source_end) return -1; val = *src; read_two_nibbles = 1; } else { val = saved_val; read_two_nibbles = 0; } if (!(val & 0xC)) { for (;;) { if (!read_two_nibbles) { if (src < source || src >= source_end) return -1; shift += 2; val |= *src << shift; if (*src & 0xC) break; } // two upper bits of the nibble is zero, // so shift top nibble value down into their place read_two_nibbles = 0; shift += 2; mask = (1 << shift) - 1; val = ((val >> 2) & ~mask) | (val & mask); NEXT_BYTE(src); if ((val & (0xC << shift))) { flag = 1; break; } } } else if (mode) { flag = tmplen != 4; } if (flag) { tmplen = 4; } else { saved_val = val >> (4 + shift); tmplen = 0; val &= (1 << (shift + 4)) - 1; NEXT_BYTE(src); } advance_mode = val & 1; len = (val >> 1) - 1; mode += 1 + advance_mode; if (mode >= 4) mode -= 3; if (FFABS(dst_end - dst) < len) return -1; switch (mode) { case 1: if (forward) { if (dst - frame + SCREEN_WIDE < frame_off || frame_end - dst < frame_off + len) return -1; for (i = 0; i < len; i++) dst[i] = dst[frame_off + i]; dst += len; } else { dst -= len; if (dst - frame + SCREEN_WIDE < frame_off || frame_end - dst < frame_off + len) return -1; for (i = len - 1; i >= 0; i--) dst[i] = dst[frame_off + i]; } break; case 2: if (forward) { if (source + src_len - src < len) return -1; memcpy(dst, src, len); dst += len; src += len; } else { if (src - source < len) return -1; dst -= len; src -= len; memcpy(dst, src, len); } break; case 3: val = forward ? dst[-1] : dst[1]; if (forward) { memset(dst, val, len); dst += len; } else { dst -= len; memset(dst, val, len); } break; default: break; } if (dst == dst_end) return 0; } return 0; }