You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
585 lines
18 KiB
585 lines
18 KiB
/*
|
|
* Copyright 2012 dorkbox, llc
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
package dorkbox.cabParser.decompress.lzx;
|
|
|
|
import dorkbox.cabParser.CabException;
|
|
import dorkbox.cabParser.CorruptCabException;
|
|
import dorkbox.cabParser.decompress.Decompressor;
|
|
|
|
public final class DecompressLzx implements Decompressor, LZXConstants {
|
|
private int[] extraBits = new int[51];
|
|
private int[] positionBase = new int[51];
|
|
|
|
private DecompressLzxTree mainTree;
|
|
private DecompressLzxTree lengthTree;
|
|
private DecompressLzxTree alignedTree;
|
|
private DecompressLzxTree preTree;
|
|
|
|
private int wndSize;
|
|
private int windowMask;
|
|
private int mainElements;
|
|
private int blocksRemaining;
|
|
private int blockType;
|
|
private int blockRemaining;
|
|
|
|
private int blockLength;
|
|
private int windowPosition;
|
|
private int R0;
|
|
private int R1;
|
|
private int R2;
|
|
|
|
private byte[] localWindow;
|
|
|
|
private int windowSize;
|
|
private boolean readHeader;
|
|
|
|
private int outputPosition;
|
|
private int index;
|
|
private int length;
|
|
|
|
private byte[] inputBytes;
|
|
|
|
private boolean abort;
|
|
|
|
int bitsLeft;
|
|
|
|
private int blockAlignOffset;
|
|
private int intelFileSize;
|
|
private int intelCursorPos;
|
|
|
|
private boolean intelStarted;
|
|
private int framesRead;
|
|
|
|
public DecompressLzx() {
|
|
int i = 4;
|
|
int j = 1;
|
|
do {
|
|
this.extraBits[i] = j;
|
|
this.extraBits[i + 1] = j;
|
|
i += 2;
|
|
j++;
|
|
} while (j <= 16);
|
|
|
|
do {
|
|
this.extraBits[i++] = 17;
|
|
} while (i < 51);
|
|
|
|
i = -2;
|
|
for (j = 0; j < this.extraBits.length; j++) {
|
|
this.positionBase[j] = i;
|
|
i += 1 << this.extraBits[j];
|
|
}
|
|
|
|
this.windowSize = -1;
|
|
}
|
|
|
|
@Override
|
|
public void init(int windowBits) throws CabException {
|
|
this.wndSize = 1 << windowBits;
|
|
this.windowMask = this.wndSize - 1;
|
|
reset(windowBits);
|
|
}
|
|
|
|
@Override
|
|
public void decompress(byte[] inputBytes, byte[] outputBytes, int inputLength, int outputLength) throws CabException {
|
|
this.abort = false;
|
|
this.index = 0;
|
|
this.inputBytes = inputBytes;
|
|
this.length = inputBytes.length;
|
|
|
|
initBitStream();
|
|
|
|
int decompressedOutputLength = decompressLoop(outputLength);
|
|
System.arraycopy(this.localWindow, this.outputPosition, outputBytes, 0, decompressedOutputLength);
|
|
|
|
if (this.framesRead++ < E8_DISABLE_THRESHOLD && this.intelFileSize != 0) {
|
|
decodeIntelBlock(outputBytes, decompressedOutputLength);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public int getMaxGrowth() {
|
|
return MAX_GROWTH;
|
|
}
|
|
|
|
@Override
|
|
public void reset(int windowBits) throws CabException {
|
|
if (this.windowSize == windowBits) {
|
|
this.mainTree.reset();
|
|
this.lengthTree.reset();
|
|
this.alignedTree.reset();
|
|
}
|
|
else {
|
|
maybeReset();
|
|
int i = NUM_CHARS + this.mainElements * ALIGNED_NUM_ELEMENTS;
|
|
this.localWindow = new byte[this.wndSize + 261];
|
|
|
|
this.preTree = new DecompressLzxTree(PRETREE_NUM_ELEMENTS, ALIGNED_NUM_ELEMENTS, this, null);
|
|
this.mainTree = new DecompressLzxTree(i, 9, this, this.preTree);
|
|
this.lengthTree = new DecompressLzxTree(SECONDARY_NUM_ELEMENTS, 6, this, this.preTree);
|
|
this.alignedTree = new DecompressLzxTree(ALIGNED_NUM_ELEMENTS, NUM_PRIMARY_LENGTHS, this, this.preTree);
|
|
}
|
|
|
|
this.windowSize = windowBits;
|
|
this.R0 = this.R1 = this.R2 = 1;
|
|
|
|
this.blocksRemaining = 0;
|
|
this.windowPosition = 0;
|
|
this.intelCursorPos = 0;
|
|
|
|
this.readHeader = true;
|
|
this.intelStarted = false;
|
|
this.framesRead = 0;
|
|
this.blockType = BLOCKTYPE_INVALID;
|
|
}
|
|
|
|
private int decompressLoop(int bytesToRead) throws CabException {
|
|
int i = bytesToRead;
|
|
int lastWindowPosition;
|
|
int k;
|
|
int m;
|
|
|
|
if (this.readHeader) {
|
|
// read header
|
|
if (readBits(1) == 1) {
|
|
k = readBits(16);
|
|
m = readBits(16);
|
|
this.intelFileSize = k << 16 | m;
|
|
} else {
|
|
this.intelFileSize = 0;
|
|
}
|
|
this.readHeader = false;
|
|
}
|
|
|
|
lastWindowPosition = 0;
|
|
while (bytesToRead > 0) {
|
|
if (this.blocksRemaining == 0) {
|
|
if (this.blockType == BLOCKTYPE_UNCOMPRESSED) {
|
|
if ((this.blockLength & 0x1) != 0 && /* realign bitstream to word */
|
|
this.index < this.length) {
|
|
this.index++;
|
|
}
|
|
this.blockType = BLOCKTYPE_INVALID;
|
|
initBitStream();
|
|
}
|
|
|
|
this.blockType = readBits(3);
|
|
k = readBits(8);
|
|
m = readBits(8);
|
|
int n = readBits(8);
|
|
|
|
if (this.abort) {
|
|
break;
|
|
}
|
|
|
|
this.blockRemaining = this.blockLength = (k << 16) + (m << 8) + n;
|
|
|
|
if (this.blockType == BLOCKTYPE_ALIGNED) {
|
|
this.alignedTree.readLengths();
|
|
this.alignedTree.buildTable();
|
|
// rest of aligned header is same as verbatim
|
|
}
|
|
|
|
if (this.blockType == BLOCKTYPE_ALIGNED || this.blockType == BLOCKTYPE_VERBATIM) {
|
|
this.mainTree.read();
|
|
this.lengthTree.read();
|
|
|
|
this.mainTree.readLengths(0, NUM_CHARS);
|
|
this.mainTree.readLengths(NUM_CHARS, NUM_CHARS + this.mainElements * ALIGNED_NUM_ELEMENTS);
|
|
this.mainTree.buildTable();
|
|
|
|
|
|
if (this.mainTree.LENS[0xE8] != 0) {
|
|
this.intelStarted = true;
|
|
}
|
|
|
|
this.lengthTree.readLengths(0, SECONDARY_NUM_ELEMENTS);
|
|
this.lengthTree.buildTable();
|
|
}
|
|
else if (this.blockType == BLOCKTYPE_UNCOMPRESSED) {
|
|
// because we can't assume otherwise
|
|
this.intelStarted = true;
|
|
this.index -= 2; // align the bitstream
|
|
|
|
|
|
if (this.index < 0 || this.index + 12 >= this.length) {
|
|
throw new CorruptCabException();
|
|
}
|
|
|
|
this.R0 = readInt();
|
|
this.R1 = readInt();
|
|
this.R2 = readInt();
|
|
}
|
|
else {
|
|
throw new CorruptCabException();
|
|
}
|
|
}
|
|
this.blocksRemaining = 1;
|
|
|
|
while (this.blockRemaining > 0 && bytesToRead > 0) {
|
|
if (this.blockRemaining < bytesToRead) {
|
|
k = this.blockRemaining;
|
|
} else {
|
|
k = bytesToRead;
|
|
}
|
|
decompressBlockActions(k);
|
|
|
|
this.blockRemaining -= k;
|
|
bytesToRead -= k;
|
|
lastWindowPosition += k;
|
|
}
|
|
|
|
if (this.blockRemaining == 0) {
|
|
this.blocksRemaining = 0;
|
|
}
|
|
|
|
if (bytesToRead == 0 && this.blockAlignOffset != 16) {
|
|
readNumberBits(this.blockAlignOffset);
|
|
}
|
|
}
|
|
|
|
if (lastWindowPosition != i) {
|
|
throw new CorruptCabException();
|
|
}
|
|
|
|
if (this.windowPosition == 0) {
|
|
this.outputPosition = this.wndSize - lastWindowPosition;
|
|
} else {
|
|
this.outputPosition = this.windowPosition - lastWindowPosition;
|
|
}
|
|
|
|
return lastWindowPosition;
|
|
}
|
|
|
|
@SuppressWarnings("NumericCastThatLosesPrecision")
|
|
private void decodeIntelBlock(byte[] bytes, int outLength) {
|
|
if (outLength <= 10 || !this.intelStarted) {
|
|
this.intelCursorPos += outLength;
|
|
return;
|
|
}
|
|
|
|
int cursorPos = this.intelCursorPos;
|
|
int fileSize = this.intelFileSize;
|
|
int abs_off = 0;
|
|
|
|
int adjustedOutLength = outLength - 10;
|
|
|
|
int dataIndex = 0;
|
|
int cursor_pos = cursorPos + adjustedOutLength;
|
|
|
|
while (cursorPos < cursor_pos) {
|
|
while (bytes[dataIndex++] == -24) {
|
|
if (cursorPos >= cursor_pos) {
|
|
break;
|
|
}
|
|
|
|
abs_off = bytes[dataIndex] & 0xFF |
|
|
(bytes[dataIndex + 1] & 0xFF) << 8 |
|
|
(bytes[dataIndex + 2] & 0xFF) << 16 |
|
|
(bytes[dataIndex + 3] & 0xFF) << 24;
|
|
|
|
if ((abs_off >= -cursorPos) && (abs_off < fileSize)) {
|
|
int rel_off = (abs_off >= 0) ? abs_off - cursorPos : abs_off + fileSize;
|
|
bytes[dataIndex] = (byte) (rel_off & 0xFF);
|
|
bytes[dataIndex + 1] = (byte) (rel_off >>> 8 & 0xFF);
|
|
bytes[dataIndex + 2] = (byte) (rel_off >>> 16 & 0xFF);
|
|
bytes[dataIndex + 3] = (byte) (rel_off >>> 24 & 0xFF);
|
|
}
|
|
|
|
dataIndex += 4;
|
|
cursorPos += 5;
|
|
}
|
|
cursorPos++;
|
|
}
|
|
|
|
this.intelCursorPos += outLength;
|
|
}
|
|
|
|
private void decompressBlockActions(int bytesToRead) throws CabException {
|
|
this.windowPosition &= this.windowMask;
|
|
|
|
if (this.windowPosition + bytesToRead > this.wndSize) {
|
|
throw new CabException();
|
|
}
|
|
|
|
switch (this.blockType) {
|
|
case BLOCKTYPE_UNCOMPRESSED :
|
|
uncompressedAlgo(bytesToRead);
|
|
return;
|
|
case BLOCKTYPE_ALIGNED :
|
|
alignedAlgo(bytesToRead);
|
|
return;
|
|
case BLOCKTYPE_VERBATIM :
|
|
verbatimAlgo(bytesToRead);
|
|
return;
|
|
default :
|
|
throw new CorruptCabException();
|
|
}
|
|
}
|
|
|
|
|
|
void readNumberBits(int numBits) {
|
|
this.bitsLeft <<= numBits;
|
|
this.blockAlignOffset -= numBits;
|
|
|
|
if (this.blockAlignOffset <= 0) {
|
|
this.bitsLeft |= readShort() << -this.blockAlignOffset;
|
|
this.blockAlignOffset += 16;
|
|
}
|
|
}
|
|
|
|
private void initBitStream() {
|
|
if (this.blockType != BLOCKTYPE_UNCOMPRESSED) {
|
|
this.bitsLeft = readShort() << 16 | readShort();
|
|
this.blockAlignOffset = 16;
|
|
}
|
|
}
|
|
|
|
private void maybeReset() {
|
|
this.mainElements = 4;
|
|
int i = 4;
|
|
do {
|
|
i += 1 << this.extraBits[this.mainElements];
|
|
this.mainElements++;
|
|
} while (i < this.wndSize);
|
|
}
|
|
|
|
@SuppressWarnings("NumericCastThatLosesPrecision")
|
|
private void verbatimAlgo(int this_run) throws CorruptCabException {
|
|
int i = this.windowPosition;
|
|
int mask = this.windowMask;
|
|
byte[] windowPosition = this.localWindow;
|
|
int r0 = this.R0;
|
|
int r1 = this.R1;
|
|
int r2 = this.R2;
|
|
|
|
int[] arrayOfInt1 = this.extraBits;
|
|
int[] arrayOfInt2 = this.positionBase;
|
|
|
|
DecompressLzxTree mainTree = this.mainTree;
|
|
DecompressLzxTree lengthTree = this.lengthTree;
|
|
|
|
while (this_run > 0) {
|
|
int main_element = mainTree.decodeElement();
|
|
if (main_element < NUM_CHARS) {
|
|
windowPosition[i++] = (byte) main_element;
|
|
this_run--;
|
|
}
|
|
/* is a match */
|
|
else {
|
|
main_element -= NUM_CHARS;
|
|
|
|
int match_length = main_element & NUM_PRIMARY_LENGTHS;
|
|
if (match_length == NUM_PRIMARY_LENGTHS) {
|
|
match_length += lengthTree.decodeElement();
|
|
}
|
|
|
|
int matchOffset = main_element >>> 3;
|
|
|
|
/* check for repeated offsets (positions 0,1,2) */
|
|
if (matchOffset == 0) {
|
|
matchOffset = r0;
|
|
}
|
|
else if (matchOffset == 1) {
|
|
matchOffset = r1;
|
|
r1 = r0;
|
|
r0 = matchOffset;
|
|
}
|
|
else if (matchOffset > 2) {
|
|
// not repeated offset
|
|
if (matchOffset > 3) {
|
|
matchOffset = verbatimAlgo2(arrayOfInt1[matchOffset]) + arrayOfInt2[matchOffset];
|
|
} else {
|
|
matchOffset = 1;
|
|
}
|
|
|
|
r2 = r1;
|
|
r1 = r0;
|
|
r0 = matchOffset;
|
|
} else {
|
|
matchOffset = r2;
|
|
r2 = r0;
|
|
r0 = matchOffset;
|
|
}
|
|
match_length += MIN_MATCH;
|
|
|
|
this_run -= match_length;
|
|
|
|
while (match_length > 0) {
|
|
windowPosition[i] = windowPosition[i - matchOffset & mask];
|
|
i++;
|
|
match_length--;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (this_run != 0) {
|
|
throw new CorruptCabException();
|
|
}
|
|
|
|
this.R0 = r0;
|
|
this.R1 = r1;
|
|
this.R2 = r2;
|
|
this.windowPosition = i;
|
|
}
|
|
|
|
private int verbatimAlgo2(int position) {
|
|
int i = this.bitsLeft >>> 32 - position;
|
|
|
|
this.bitsLeft <<= position;
|
|
this.blockAlignOffset -= position;
|
|
|
|
if (this.blockAlignOffset <= 0) {
|
|
this.bitsLeft |= readShort() << -this.blockAlignOffset;
|
|
this.blockAlignOffset += 16;
|
|
|
|
if (this.blockAlignOffset <= 0) {
|
|
this.bitsLeft |= readShort() << -this.blockAlignOffset;
|
|
this.blockAlignOffset += 16;
|
|
}
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
@SuppressWarnings("NumericCastThatLosesPrecision")
|
|
private void alignedAlgo(int this_run) throws CorruptCabException {
|
|
int windowPos = this.windowPosition;
|
|
int mask = this.windowMask;
|
|
byte[] window = this.localWindow;
|
|
int r0 = this.R0;
|
|
int r1 = this.R1;
|
|
int r2 = this.R2;
|
|
|
|
while (this_run > 0) {
|
|
int mainElement = this.mainTree.decodeElement();
|
|
|
|
if (mainElement < NUM_CHARS) {
|
|
window[windowPos] = (byte) mainElement;
|
|
windowPos = windowPos + 1 & mask;
|
|
this_run--;
|
|
}
|
|
/* is a match */
|
|
else {
|
|
mainElement -= NUM_CHARS;
|
|
|
|
int matchLength = mainElement & NUM_PRIMARY_LENGTHS;
|
|
if (matchLength == NUM_PRIMARY_LENGTHS) {
|
|
matchLength += this.lengthTree.decodeElement();
|
|
}
|
|
|
|
int match_offset = mainElement >>> 3;
|
|
|
|
if (match_offset > 2) {
|
|
// not repeated offset
|
|
int extra = this.extraBits[match_offset];
|
|
match_offset = this.positionBase[match_offset];
|
|
|
|
if (extra > 3) {
|
|
// verbatim and aligned bits
|
|
match_offset += (readBits(extra - 3) << 3) + this.alignedTree.decodeElement();
|
|
}
|
|
else if (extra == 3) {
|
|
// aligned bits only
|
|
match_offset += this.alignedTree.decodeElement();
|
|
}
|
|
else if (extra > 0) {
|
|
// verbatim bits only
|
|
match_offset += readBits(extra);
|
|
}
|
|
else {
|
|
match_offset = 1;
|
|
}
|
|
|
|
// update repeated offset LRU queue
|
|
r2 = r1;
|
|
r1 = r0;
|
|
r0 = match_offset;
|
|
}
|
|
else if (match_offset == 0) {
|
|
match_offset = r0;
|
|
}
|
|
else if (match_offset == 1) {
|
|
match_offset = r1;
|
|
r1 = r0;
|
|
r0 = match_offset;
|
|
} else {
|
|
match_offset = r2;
|
|
r2 = r0;
|
|
r0 = match_offset;
|
|
}
|
|
|
|
matchLength += MIN_MATCH;
|
|
this_run -= matchLength;
|
|
|
|
while (matchLength > 0) {
|
|
window[windowPos] = window[windowPos - match_offset & mask];
|
|
windowPos = windowPos + 1 & mask;
|
|
matchLength--;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (this_run != 0) {
|
|
throw new CorruptCabException();
|
|
}
|
|
|
|
this.R0 = r0;
|
|
this.R1 = r1;
|
|
this.R2 = r2;
|
|
this.windowPosition = windowPos;
|
|
}
|
|
|
|
private int readShort() {
|
|
if (this.index < this.length) {
|
|
int i = this.inputBytes[this.index] & 0xFF | (this.inputBytes[this.index + 1] & 0xFF) << 8;
|
|
this.index += 2;
|
|
return i;
|
|
}
|
|
|
|
this.abort = true;
|
|
this.index = 0;
|
|
return 0;
|
|
}
|
|
|
|
int readBits(int numBitsToRead) {
|
|
int i = this.bitsLeft >>> 32 - numBitsToRead;
|
|
readNumberBits(numBitsToRead);
|
|
return i;
|
|
}
|
|
|
|
private void uncompressedAlgo(int length) throws CorruptCabException {
|
|
if (this.index + length > this.length || this.windowPosition + length > this.wndSize) {
|
|
throw new CorruptCabException();
|
|
}
|
|
|
|
this.intelStarted = true;
|
|
System.arraycopy(this.inputBytes, this.index, this.localWindow, this.windowPosition, length);
|
|
this.index += length;
|
|
this.windowPosition += length;
|
|
}
|
|
|
|
private int readInt() {
|
|
int i = this.inputBytes[this.index] & 0xFF |
|
|
(this.inputBytes[this.index + 1] & 0xFF) << 8 |
|
|
(this.inputBytes[this.index + 2] & 0xFF) << 16 |
|
|
(this.inputBytes[this.index + 3] & 0xFF) << 24;
|
|
this.index += 4;
|
|
|
|
return i;
|
|
}
|
|
}
|