mirror of
https://github.com/tsolomko/SWCompression.git
synced 2026-06-23 14:56:41 +00:00
102 lines
3.5 KiB
Swift
102 lines
3.5 KiB
Swift
// Copyright (c) 2026 Timofey Solomko
|
|
// Licensed under MIT License
|
|
//
|
|
// See LICENSE for license information
|
|
|
|
import BitByteData
|
|
|
|
struct LZMA2Decoder {
|
|
|
|
private let byteReader: LittleEndianByteReader
|
|
private var decoder: LZMADecoder
|
|
|
|
var out: [UInt8] {
|
|
return self.decoder.out
|
|
}
|
|
|
|
init(_ byteReader: LittleEndianByteReader, _ dictSizeByte: UInt8) throws {
|
|
self.byteReader = byteReader
|
|
self.decoder = LZMADecoder(byteReader)
|
|
|
|
guard dictSizeByte & 0xC0 == 0
|
|
else { throw LZMA2Error.wrongDictionarySize }
|
|
let bits = (dictSizeByte & 0x3F).toInt()
|
|
guard bits < 40
|
|
else { throw LZMA2Error.wrongDictionarySize }
|
|
|
|
let dictSize = bits == 40 ? UInt32.max :
|
|
(UInt32(truncatingIfNeeded: 2 | (bits & 1)) << UInt32(truncatingIfNeeded: bits / 2 + 11))
|
|
|
|
self.decoder.properties.dictionarySize = dictSize.toInt()
|
|
}
|
|
|
|
/// Main LZMA2 decoder function.
|
|
mutating func decode() throws {
|
|
mainLoop: while true {
|
|
let controlByte = byteReader.byte()
|
|
switch controlByte {
|
|
case 0:
|
|
break mainLoop
|
|
case 1:
|
|
self.decoder.resetDictionary()
|
|
self.decodeUncompressed()
|
|
case 2:
|
|
self.decodeUncompressed()
|
|
case 3...0x7F:
|
|
throw LZMA2Error.wrongControlByte
|
|
case 0x80...0xFF:
|
|
try self.dispatch(controlByte)
|
|
default:
|
|
fatalError("Incorrect control byte.") // This statement is never executed.
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Function which dispatches LZMA2 decoding process based on `controlByte`.
|
|
private mutating func dispatch(_ controlByte: UInt8) throws {
|
|
let uncompressedSizeBits = controlByte & 0x1F
|
|
let reset = (controlByte & 0x60) >> 5
|
|
let unpackSize = (uncompressedSizeBits.toInt() << 16) +
|
|
self.byteReader.byte().toInt() << 8 + self.byteReader.byte().toInt() + 1
|
|
let compressedSize = self.byteReader.byte().toInt() << 8 + self.byteReader.byte().toInt() + 1
|
|
switch reset {
|
|
case 0:
|
|
break
|
|
case 1:
|
|
self.decoder.resetStateAndDecoders()
|
|
case 2:
|
|
try self.updateProperties()
|
|
case 3:
|
|
try self.updateProperties()
|
|
self.decoder.resetDictionary()
|
|
default:
|
|
throw LZMA2Error.wrongReset
|
|
}
|
|
self.decoder.uncompressedSize = unpackSize
|
|
let outStartIndex = self.decoder.out.count
|
|
let inStartIndex = self.byteReader.offset
|
|
try self.decoder.decode()
|
|
guard unpackSize == self.decoder.out.count - outStartIndex &&
|
|
self.byteReader.offset - inStartIndex == compressedSize
|
|
else { throw LZMA2Error.wrongSizes }
|
|
}
|
|
|
|
private mutating func decodeUncompressed() {
|
|
let dataSize = self.byteReader.byte().toInt() << 8 + self.byteReader.byte().toInt() + 1
|
|
for _ in 0..<dataSize {
|
|
self.decoder.put(self.byteReader.byte())
|
|
}
|
|
}
|
|
|
|
/**
|
|
Sets `lc`, `pb` and `lp` properties of LZMA decoder with a single `byte` using standard LZMA properties encoding
|
|
scheme and resets decoder's state and sub-decoders.
|
|
*/
|
|
private mutating func updateProperties() throws {
|
|
self.decoder.properties = try LZMAProperties(lzmaByte: byteReader.byte(),
|
|
self.decoder.properties.dictionarySize)
|
|
self.decoder.resetStateAndDecoders()
|
|
}
|
|
|
|
}
|