+ u8 *out_next = out;
+ u8 * const out_end = out + out_nbytes;
+
+ while (out_next != out_end) {
+
+ if (!lzms_decode_main_bit(d)) {
+
+ /* Literal */
+ *out_next++ = lzms_decode_literal(d);
+
+ } else if (!lzms_decode_match_bit(d)) {
+
+ /* LZ match */
+
+ u32 offset;
+ u32 length;
+
+ if (d->pending_lz_offset != 0 &&
+ out_next != d->lz_offset_still_pending)
+ {
+ BUILD_BUG_ON(LZMS_NUM_RECENT_OFFSETS != 3);
+ d->recent_lz_offsets[3] = d->recent_lz_offsets[2];
+ d->recent_lz_offsets[2] = d->recent_lz_offsets[1];
+ d->recent_lz_offsets[1] = d->recent_lz_offsets[0];
+ d->recent_lz_offsets[0] = d->pending_lz_offset;
+ d->pending_lz_offset = 0;
+ }
+
+ if (!lzms_decode_lz_match_bit(d)) {
+ /* Explicit offset */
+ offset = lzms_decode_lz_offset(d);
+ } else {
+ /* Repeat offset */
+
+ BUILD_BUG_ON(LZMS_NUM_RECENT_OFFSETS != 3);
+ if (!lzms_decode_lz_repeat_match_bit(d, 0)) {
+ offset = d->recent_lz_offsets[0];
+ d->recent_lz_offsets[0] = d->recent_lz_offsets[1];
+ d->recent_lz_offsets[1] = d->recent_lz_offsets[2];
+ d->recent_lz_offsets[2] = d->recent_lz_offsets[3];
+ } else if (!lzms_decode_lz_repeat_match_bit(d, 1)) {
+ offset = d->recent_lz_offsets[1];
+ d->recent_lz_offsets[1] = d->recent_lz_offsets[2];
+ d->recent_lz_offsets[2] = d->recent_lz_offsets[3];
+ } else {
+ offset = d->recent_lz_offsets[2];
+ d->recent_lz_offsets[2] = d->recent_lz_offsets[3];
+ }
+ }
+
+ if (d->pending_lz_offset != 0) {
+ BUILD_BUG_ON(LZMS_NUM_RECENT_OFFSETS != 3);
+ d->recent_lz_offsets[3] = d->recent_lz_offsets[2];
+ d->recent_lz_offsets[2] = d->recent_lz_offsets[1];
+ d->recent_lz_offsets[1] = d->recent_lz_offsets[0];
+ d->recent_lz_offsets[0] = d->pending_lz_offset;
+ }
+ d->pending_lz_offset = offset;
+
+ length = lzms_decode_length(d);
+
+ if (unlikely(length > out_end - out_next))
+ return -1;
+ if (unlikely(offset > out_next - out))
+ return -1;
+
+ lz_copy(out_next, length, offset, out_end, LZMS_MIN_MATCH_LEN);
+ out_next += length;
+
+ d->lz_offset_still_pending = out_next;
+ } else {
+ /* Delta match */
+
+ u32 power;
+ u32 raw_offset, offset1, offset2, offset;
+ const u8 *matchptr1, *matchptr2, *matchptr;
+ u32 length;
+
+ if (d->pending_delta_offset != 0 &&
+ out_next != d->delta_offset_still_pending)
+ {
+ BUILD_BUG_ON(LZMS_NUM_RECENT_OFFSETS != 3);
+ d->recent_delta_offsets[3] = d->recent_delta_offsets[2];
+ d->recent_delta_offsets[2] = d->recent_delta_offsets[1];
+ d->recent_delta_offsets[1] = d->recent_delta_offsets[0];
+ d->recent_delta_offsets[0] = d->pending_delta_offset;
+ d->pending_delta_offset = 0;
+ }
+
+ if (!lzms_decode_delta_match_bit(d)) {
+ /* Explicit offset */
+ power = lzms_decode_delta_power(d);
+ raw_offset = lzms_decode_delta_offset(d);
+ } else {
+ /* Repeat offset */
+ u64 val;
+
+ BUILD_BUG_ON(LZMS_NUM_RECENT_OFFSETS != 3);
+ if (!lzms_decode_delta_repeat_match_bit(d, 0)) {
+ val = d->recent_delta_offsets[0];
+ d->recent_delta_offsets[0] = d->recent_delta_offsets[1];
+ d->recent_delta_offsets[1] = d->recent_delta_offsets[2];
+ d->recent_delta_offsets[2] = d->recent_delta_offsets[3];
+ } else if (!lzms_decode_delta_repeat_match_bit(d, 1)) {
+ val = d->recent_delta_offsets[1];
+ d->recent_delta_offsets[1] = d->recent_delta_offsets[2];
+ d->recent_delta_offsets[2] = d->recent_delta_offsets[3];
+ } else {
+ val = d->recent_delta_offsets[2];
+ d->recent_delta_offsets[2] = d->recent_delta_offsets[3];
+ }
+ power = val >> 32;
+ raw_offset = (u32)val;
+ }
+
+ if (d->pending_delta_offset != 0) {
+ BUILD_BUG_ON(LZMS_NUM_RECENT_OFFSETS != 3);
+ d->recent_delta_offsets[3] = d->recent_delta_offsets[2];
+ d->recent_delta_offsets[2] = d->recent_delta_offsets[1];
+ d->recent_delta_offsets[1] = d->recent_delta_offsets[0];
+ d->recent_delta_offsets[0] = d->pending_delta_offset;
+ d->pending_delta_offset = 0;
+ }
+ d->pending_delta_offset = raw_offset | ((u64)power << 32);
+
+ length = lzms_decode_length(d);
+
+ offset1 = (u32)1 << power;
+ offset2 = raw_offset << power;
+ offset = offset1 + offset2;
+
+ /* raw_offset<<power overflowed? */
+ if (unlikely((offset2 >> power) != raw_offset))
+ return -1;
+
+ /* offset1+offset2 overflowed? */
+ if (unlikely(offset < offset2))
+ return -1;
+
+ if (unlikely(length > out_end - out_next))
+ return -1;
+
+ if (unlikely(offset > out_next - out))
+ return -1;
+
+ matchptr1 = out_next - offset1;
+ matchptr2 = out_next - offset2;
+ matchptr = out_next - offset;
+
+ do {
+ *out_next++ = *matchptr1++ + *matchptr2++ - *matchptr++;
+ } while (--length);
+
+ d->delta_offset_still_pending = out_next;