diff options
| author | Ophestra <cat@gensokyo.uk> | 2026-07-25 14:03:03 +0900 |
|---|---|---|
| committer | Ophestra <cat@gensokyo.uk> | 2026-07-25 14:03:34 +0900 |
| commit | e5abf51296fa1405c4a6314eafc1b97d247c5b60 (patch) | |
| tree | 8e54f3a684dd0b875756b3a4eca13cd79ab695f1 /internal/xz/dec_bcj.go | |
| parent | e26914cd6f86341daff4c3d7282d9a8cf8e4611a (diff) | |
internal/pkg: decompress xz streams
This uses a port of the public domain xz implementation.
Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'internal/xz/dec_bcj.go')
| -rw-r--r-- | internal/xz/dec_bcj.go | 461 |
1 files changed, 461 insertions, 0 deletions
diff --git a/internal/xz/dec_bcj.go b/internal/xz/dec_bcj.go new file mode 100644 index 00000000..a8a3df92 --- /dev/null +++ b/internal/xz/dec_bcj.go @@ -0,0 +1,461 @@ +/* + * Branch/Call/Jump (BCJ) filter decoders + * + * Authors: Lasse Collin <lasse.collin@tukaani.org> + * Igor Pavlov <http://7-zip.org/> + * + * Translation to Go: Michael Cross <https://github.com/xi2> + * + * This file has been put into the public domain. + * You can do whatever you want with this file. + */ + +package xz + +/* from linux/lib/xz/xz_dec_bcj.c *************************************/ + +type xzDecBCJ struct { + /* Type of the BCJ filter being used */ + typ xzFilterID + /* + * Return value of the next filter in the chain. We need to preserve + * this information across calls, because we must not call the next + * filter anymore once it has returned xzStreamEnd + */ + ret xzRet + /* + * Absolute position relative to the beginning of the uncompressed + * data (in a single .xz Block). + */ + pos int + /* x86 filter state */ + x86PrevMask uint32 + /* Temporary space to hold the variables from xzBuf */ + out []byte + outPos int + temp struct { + /* Amount of already filtered data in the beginning of buf */ + filtered int + /* + * Buffer to hold a mix of filtered and unfiltered data. This + * needs to be big enough to hold Alignment + 2 * Look-ahead: + * + * Type Alignment Look-ahead + * x86 1 4 + * PowerPC 4 0 + * IA-64 16 0 + * ARM 4 0 + * ARM-Thumb 2 2 + * SPARC 4 0 + */ + buf []byte // slice buf will be backed by bufArray + bufArray [16]byte + } +} + +/* + * This is used to test the most significant byte of a memory address + * in an x86 instruction. + */ +func bcjX86TestMSByte(b byte) bool { + return b == 0x00 || b == 0xff +} + +func bcjX86Filter(s *xzDecBCJ, buf []byte) int { + var maskToAllowedStatus = []bool{ + true, true, true, false, true, false, false, false, + } + var maskToBitNum = []byte{0, 1, 2, 2, 3, 3, 3, 3} + var i int + var prevPos int = -1 + var prevMask uint32 = s.x86PrevMask + var src uint32 + var dest uint32 + var j uint32 + var b byte + if len(buf) <= 4 { + return 0 + } + for i = 0; i < len(buf)-4; i++ { + if buf[i]&0xfe != 0xe8 { + continue + } + prevPos = i - prevPos + if prevPos > 3 { + prevMask = 0 + } else { + prevMask = (prevMask << (uint(prevPos) - 1)) & 7 + if prevMask != 0 { + b = buf[i+4-int(maskToBitNum[prevMask])] + if !maskToAllowedStatus[prevMask] || bcjX86TestMSByte(b) { + prevPos = i + prevMask = prevMask<<1 | 1 + continue + } + } + } + prevPos = i + if bcjX86TestMSByte(buf[i+4]) { + src = getLE32(buf[i+1:]) + for { + dest = src - uint32(s.pos+i+5) + if prevMask == 0 { + break + } + j = uint32(maskToBitNum[prevMask]) * 8 + b = byte(dest >> (24 - j)) + if !bcjX86TestMSByte(b) { + break + } + src = dest ^ (1<<(32-j) - 1) + } + dest &= 0x01FFFFFF + dest |= 0 - dest&0x01000000 + putLE32(dest, buf[i+1:]) + i += 4 + } else { + prevMask = prevMask<<1 | 1 + } + } + prevPos = i - prevPos + if prevPos > 3 { + s.x86PrevMask = 0 + } else { + s.x86PrevMask = prevMask << (uint(prevPos) - 1) + } + return i +} + +func bcjPowerPCFilter(s *xzDecBCJ, buf []byte) int { + var i int + var instr uint32 + for i = 0; i+4 <= len(buf); i += 4 { + instr = getBE32(buf[i:]) + if instr&0xFC000003 == 0x48000001 { + instr &= 0x03FFFFFC + instr -= uint32(s.pos + i) + instr &= 0x03FFFFFC + instr |= 0x48000001 + putBE32(instr, buf[i:]) + } + } + return i +} + +var bcjIA64BranchTable = [...]byte{ + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 4, 4, 6, 6, 0, 0, 7, 7, + 4, 4, 0, 0, 4, 4, 0, 0, +} + +func bcjIA64Filter(s *xzDecBCJ, buf []byte) int { + var branchTable = bcjIA64BranchTable[:] + /* + * The local variables take a little bit stack space, but it's less + * than what LZMA2 decoder takes, so it doesn't make sense to reduce + * stack usage here without doing that for the LZMA2 decoder too. + */ + /* Loop counters */ + var i int + var j int + /* Instruction slot (0, 1, or 2) in the 128-bit instruction word */ + var slot uint32 + /* Bitwise offset of the instruction indicated by slot */ + var bitPos uint32 + /* bit_pos split into byte and bit parts */ + var bytePos uint32 + var bitRes uint32 + /* Address part of an instruction */ + var addr uint32 + /* Mask used to detect which instructions to convert */ + var mask uint32 + /* 41-bit instruction stored somewhere in the lowest 48 bits */ + var instr uint64 + /* Instruction normalized with bit_res for easier manipulation */ + var norm uint64 + for i = 0; i+16 <= len(buf); i += 16 { + mask = uint32(branchTable[buf[i]&0x1f]) + for slot, bitPos = 0, 5; slot < 3; slot, bitPos = slot+1, bitPos+41 { + if (mask>>slot)&1 == 0 { + continue + } + bytePos = bitPos >> 3 + bitRes = bitPos & 7 + instr = 0 + for j = 0; j < 6; j++ { + instr |= uint64(buf[i+j+int(bytePos)]) << (8 * uint(j)) + } + norm = instr >> bitRes + if (norm>>37)&0x0f == 0x05 && (norm>>9)&0x07 == 0 { + addr = uint32((norm >> 13) & 0x0fffff) + addr |= (uint32(norm>>36) & 1) << 20 + addr <<= 4 + addr -= uint32(s.pos + i) + addr >>= 4 + norm &= ^(uint64(0x8fffff) << 13) + norm |= uint64(addr&0x0fffff) << 13 + norm |= uint64(addr&0x100000) << (36 - 20) + instr &= 1<<bitRes - 1 + instr |= norm << bitRes + for j = 0; j < 6; j++ { + buf[i+j+int(bytePos)] = byte(instr >> (8 * uint(j))) + } + } + } + } + return i +} + +func bcjARMFilter(s *xzDecBCJ, buf []byte) int { + var i int + var addr uint32 + for i = 0; i+4 <= len(buf); i += 4 { + if buf[i+3] == 0xeb { + addr = uint32(buf[i]) | uint32(buf[i+1])<<8 | + uint32(buf[i+2])<<16 + addr <<= 2 + addr -= uint32(s.pos + i + 8) + addr >>= 2 + buf[i] = byte(addr) + buf[i+1] = byte(addr >> 8) + buf[i+2] = byte(addr >> 16) + } + } + return i +} + +func bcjARMThumbFilter(s *xzDecBCJ, buf []byte) int { + var i int + var addr uint32 + for i = 0; i+4 <= len(buf); i += 2 { + if buf[i+1]&0xf8 == 0xf0 && buf[i+3]&0xf8 == 0xf8 { + addr = uint32(buf[i+1]&0x07)<<19 | + uint32(buf[i])<<11 | + uint32(buf[i+3]&0x07)<<8 | + uint32(buf[i+2]) + addr <<= 1 + addr -= uint32(s.pos + i + 4) + addr >>= 1 + buf[i+1] = byte(0xf0 | (addr>>19)&0x07) + buf[i] = byte(addr >> 11) + buf[i+3] = byte(0xf8 | (addr>>8)&0x07) + buf[i+2] = byte(addr) + i += 2 + } + } + return i +} + +func bcjSPARCFilter(s *xzDecBCJ, buf []byte) int { + var i int + var instr uint32 + for i = 0; i+4 <= len(buf); i += 4 { + instr = getBE32(buf[i:]) + if instr>>22 == 0x100 || instr>>22 == 0x1ff { + instr <<= 2 + instr -= uint32(s.pos + i) + instr >>= 2 + instr = (0x40000000 - instr&0x400000) | + 0x40000000 | (instr & 0x3FFFFF) + putBE32(instr, buf[i:]) + } + } + return i +} + +/* + * Apply the selected BCJ filter. Update *pos and s.pos to match the amount + * of data that got filtered. + */ +func bcjApply(s *xzDecBCJ, buf []byte, pos *int) { + var filtered int + buf = buf[*pos:] + switch s.typ { + case idBCJX86: + filtered = bcjX86Filter(s, buf) + case idBCJPowerPC: + filtered = bcjPowerPCFilter(s, buf) + case idBCJIA64: + filtered = bcjIA64Filter(s, buf) + case idBCJARM: + filtered = bcjARMFilter(s, buf) + case idBCJARMThumb: + filtered = bcjARMThumbFilter(s, buf) + case idBCJSPARC: + filtered = bcjSPARCFilter(s, buf) + default: + /* Never reached */ + } + *pos += filtered + s.pos += filtered +} + +/* + * Flush pending filtered data from temp to the output buffer. + * Move the remaining mixture of possibly filtered and unfiltered + * data to the beginning of temp. + */ +func bcjFlush(s *xzDecBCJ, b *xzBuf) { + var copySize int + copySize = len(b.out) - b.outPos + if copySize > s.temp.filtered { + copySize = s.temp.filtered + } + copy(b.out[b.outPos:], s.temp.buf[:copySize]) + b.outPos += copySize + s.temp.filtered -= copySize + copy(s.temp.buf, s.temp.buf[copySize:]) + s.temp.buf = s.temp.buf[:len(s.temp.buf)-copySize] +} + +/* + * Decode raw stream which has a BCJ filter as the first filter. + * + * The BCJ filter functions are primitive in sense that they process the + * data in chunks of 1-16 bytes. To hide this issue, this function does + * some buffering. + */ +func xzDecBCJRun(s *xzDecBCJ, b *xzBuf, chain func(*xzBuf) xzRet) xzRet { + var outStart int + /* + * Flush pending already filtered data to the output buffer. Return + * immediately if we couldn't flush everything, or if the next + * filter in the chain had already returned xzStreamEnd. + */ + if s.temp.filtered > 0 { + bcjFlush(s, b) + if s.temp.filtered > 0 { + return xzOK + } + if s.ret == xzStreamEnd { + return xzStreamEnd + } + } + /* + * If we have more output space than what is currently pending in + * temp, copy the unfiltered data from temp to the output buffer + * and try to fill the output buffer by decoding more data from the + * next filter in the chain. Apply the BCJ filter on the new data + * in the output buffer. If everything cannot be filtered, copy it + * to temp and rewind the output buffer position accordingly. + * + * This needs to be always run when len(temp.buf) == 0 to handle a special + * case where the output buffer is full and the next filter has no + * more output coming but hasn't returned xzStreamEnd yet. + */ + if len(s.temp.buf) < len(b.out)-b.outPos || len(s.temp.buf) == 0 { + outStart = b.outPos + copy(b.out[b.outPos:], s.temp.buf) + b.outPos += len(s.temp.buf) + s.ret = chain(b) + if s.ret != xzStreamEnd && s.ret != xzOK { + return s.ret + } + bcjApply(s, b.out[:b.outPos], &outStart) + /* + * As an exception, if the next filter returned xzStreamEnd, + * we can do that too, since the last few bytes that remain + * unfiltered are meant to remain unfiltered. + */ + if s.ret == xzStreamEnd { + return xzStreamEnd + } + s.temp.buf = s.temp.bufArray[:b.outPos-outStart] + b.outPos -= len(s.temp.buf) + copy(s.temp.buf, b.out[b.outPos:]) + /* + * If there wasn't enough input to the next filter to fill + * the output buffer with unfiltered data, there's no point + * to try decoding more data to temp. + */ + if b.outPos+len(s.temp.buf) < len(b.out) { + return xzOK + } + } + /* + * We have unfiltered data in temp. If the output buffer isn't full + * yet, try to fill the temp buffer by decoding more data from the + * next filter. Apply the BCJ filter on temp. Then we hopefully can + * fill the actual output buffer by copying filtered data from temp. + * A mix of filtered and unfiltered data may be left in temp; it will + * be taken care on the next call to this function. + */ + if b.outPos < len(b.out) { + /* Make b.out temporarily point to s.temp. */ + s.out = b.out + s.outPos = b.outPos + b.out = s.temp.bufArray[:] + b.outPos = len(s.temp.buf) + s.ret = chain(b) + s.temp.buf = s.temp.bufArray[:b.outPos] + b.out = s.out + b.outPos = s.outPos + if s.ret != xzOK && s.ret != xzStreamEnd { + return s.ret + } + bcjApply(s, s.temp.buf, &s.temp.filtered) + /* + * If the next filter returned xzStreamEnd, we mark that + * everything is filtered, since the last unfiltered bytes + * of the stream are meant to be left as is. + */ + if s.ret == xzStreamEnd { + s.temp.filtered = len(s.temp.buf) + } + bcjFlush(s, b) + if s.temp.filtered > 0 { + return xzOK + } + } + return s.ret +} + +/* + * Allocate memory for BCJ decoders. xzDecBCJReset must be used before + * calling xzDecBCJRun. + */ +func xzDecBCJCreate() *xzDecBCJ { + return new(xzDecBCJ) +} + +/* + * Decode the Filter ID of a BCJ filter and check the start offset is + * valid. Returns xzOK if the given Filter ID and offset is + * supported. Otherwise xzOptionsError is returned. + */ +func xzDecBCJReset(s *xzDecBCJ, id xzFilterID, offset int) xzRet { + switch id { + case idBCJX86: + case idBCJPowerPC: + case idBCJIA64: + case idBCJARM: + case idBCJARMThumb: + case idBCJSPARC: + default: + /* Unsupported Filter ID */ + return xzOptionsError + } + // check offset is a multiple of alignment + switch id { + case idBCJPowerPC, idBCJARM, idBCJSPARC: + if offset%4 != 0 { + return xzOptionsError + } + case idBCJIA64: + if offset%16 != 0 { + return xzOptionsError + } + case idBCJARMThumb: + if offset%2 != 0 { + return xzOptionsError + } + } + s.typ = id + s.ret = xzOK + s.pos = offset + s.x86PrevMask = 0 + s.temp.filtered = 0 + s.temp.buf = nil + return xzOK +} |
