/*
This file is part of jpcsp.
Jpcsp is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Jpcsp is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Jpcsp. If not, see <http://www.gnu.org/licenses/>.
*/
package jpcsp.format.rco;
import java.util.Arrays;
/**
* LZR decompression
* Based on libLZR Version 0.11 by BenHur - http://www.psp-programming.com/benhur
* https://github.com/Grumbel/rfactortools/blob/master/other/quickbms/src/compression/libLZR.c
*/
public class LZR {
private static int u8(byte b) {
return b & 0xFF;
}
private static long u32(int i) {
return i & 0xFFFFFFFFL;
}
private static class IntObject {
private static final IntObject Null = new IntObject(0);
private int value;
public IntObject(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public int incr() {
return value++;
}
public void sub(int sub) {
value -= sub;
}
@Override
public String toString() {
return String.format("0x%08X", value);
}
}
private static class BoolObject {
private boolean value;
public boolean getValue() {
return value;
}
public void setValue(boolean value) {
this.value = value;
}
@Override
public String toString() {
return Boolean.toString(value);
}
}
private static void fillBuffer(IntObject testMask, IntObject mask, IntObject buffer, byte[] in, IntObject nextIn) {
// if necessary, fill up in buffer and shift mask
if (testMask.getValue() >= 0 && testMask.getValue() <= 0x00FFFFFF) {
buffer.setValue((buffer.getValue() << 8) + u8(in[nextIn.incr()]));
mask.setValue(testMask.getValue() << 8);
}
}
private static boolean nextBit(byte[] buf, int bufPtr1, IntObject number, IntObject testMask, IntObject mask, IntObject buffer, byte[] in, IntObject nextIn) {
fillBuffer(testMask, mask, buffer, in, nextIn);
int value = (mask.getValue() >>> 8) * u8(buf[bufPtr1]);
if (testMask != mask) {
testMask.setValue(value);
}
buf[bufPtr1] -= u8(buf[bufPtr1]) >> 3;
number.setValue(number.getValue() << 1);
if (u32(buffer.getValue()) < u32(value)) {
mask.setValue(value);
buf[bufPtr1] += 31;
number.incr();
return true;
}
buffer.sub(value);
mask.sub(value);
return false;
}
private static int getNumber(int nBits, byte[] buf, int bufPtr, int inc, BoolObject flag, IntObject mask, IntObject buffer, byte[] in, IntObject nextIn) {
// Extract and return a number (consisting of n_bits bits) from in stream
IntObject number = new IntObject(1);
if (nBits >= 3) {
nextBit(buf, bufPtr + 3 * inc, number, mask, mask, buffer, in, nextIn);
if (nBits >= 4) {
nextBit(buf, bufPtr + 3 * inc, number, mask, mask, buffer, in, nextIn);
if (nBits >= 5) {
fillBuffer(mask, mask, buffer, in, nextIn);
for (; nBits >= 5; nBits--) {
number.setValue(number.getValue() << 1);
mask.setValue(mask.getValue() >>> 1);
if (u32(buffer.getValue()) < u32(mask.getValue())) {
number.incr();
} else {
buffer.sub(mask.getValue());
}
}
}
}
}
flag.setValue(nextBit(buf, bufPtr, number, mask, mask, buffer, in, nextIn));
if (nBits >= 1) {
nextBit(buf, bufPtr + inc, number, mask, mask, buffer, in, nextIn);
if (nBits >= 2) {
nextBit(buf, bufPtr + 2 * inc, number, mask, mask, buffer, in, nextIn);
}
}
return number.getValue();
}
public static int decompress(byte[] out, int outCapacity, byte[] in) {
int type = in[0];
IntObject buffer = new IntObject((u8(in[1]) << 24) | (u8(in[2]) << 16) | (u8(in[3]) << 8) | u8(in[4]));
IntObject nextIn = new IntObject(5);
int nextOut = 0;
int outEnd = outCapacity;
if (type < 0) {
// copy from stream without decompression
int seqEnd = nextOut + buffer.getValue();
if (seqEnd > outEnd) {
return -1;
}
while (nextOut < seqEnd) {
out[nextOut++] = in[nextIn.incr()];
}
return nextOut;
}
// Create and inti buffer
byte buf[] = new byte[2800];
Arrays.fill(buf, (byte) 0x80);
int bufOff = 0;
IntObject mask = new IntObject(0xFFFFFFFF);
IntObject testMask = new IntObject(0);
int lastChar = 0;
while (true) {
int bufPtr1 = bufOff + 2488;
if (!nextBit(buf, bufPtr1, IntObject.Null, mask, mask, buffer, in, nextIn)) {
// Single new char
if (bufOff > 0) {
bufOff--;
}
if (nextOut == outEnd) {
return -1;
}
bufPtr1 = (((((nextOut & 0x07) << 8) + lastChar) >> type) & 0x07) * 0xFF - 0x01;
IntObject j = new IntObject(1);
while (j.getValue() <= 0xFF) {
nextBit(buf, bufPtr1 + j.getValue(), j, mask, mask, buffer, in, nextIn);
}
out[nextOut++] = (byte) j.getValue();
} else {
// Sequence of chars that exists in out stream
// Find number of bits of sequence length
testMask.setValue(mask.getValue());
int nBits = -1;
BoolObject flag = new BoolObject();
do {
bufPtr1 += 8;
flag.setValue(nextBit(buf, bufPtr1, IntObject.Null, testMask, mask, buffer, in, nextIn));
if (flag.getValue()) {
nBits++;
}
} while (flag.getValue() && nBits < 6);
// Find sequence length
int bufPtr2 = nBits + 2033;
int j = 64;
int seqLen;
if (flag.getValue() || nBits >= 0) {
bufPtr1 = (nBits << 5) + (((nextOut << nBits) & 0x03) << 3) + bufOff + 2552;
seqLen = getNumber(nBits, buf, bufPtr1, 8, flag, mask, buffer, in, nextIn);
if (seqLen == 0xFF) {
return nextOut; // End of data stream
}
if (flag.getValue() || nBits > 0) {
bufPtr2 += 56;
j = 352;
}
} else {
seqLen = 1;
}
// Find number of bits of sequence offset
IntObject i = new IntObject(1);
do {
nBits = (i.getValue() << 4) - j;
flag.setValue(nextBit(buf, bufPtr2 + (i.getValue() << 3), i, mask, mask, buffer, in, nextIn));
} while (nBits < 0);
// Find sequence offset
int seqOff;
if (flag.getValue() || nBits > 0) {
if (!flag.getValue()) {
nBits -= 8;
}
seqOff = getNumber(nBits / 8, buf, nBits + 2344, 1, flag, mask, buffer, in, nextIn);
} else {
seqOff = 1;
}
// Copy sequence
int nextSeq = nextOut - seqOff;
if (nextSeq < 0) {
return -1;
}
int seqEnd = nextOut + seqLen + 1;
if (seqEnd > outEnd) {
return -1;
}
bufOff = ((seqEnd + 1) & 0x01) + 0x06;
do {
out[nextOut++] = out[nextSeq++];
} while (nextOut < seqEnd);
}
lastChar = u8(out[nextOut - 1]);
}
}
}