/* 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.util; import org.apache.log4j.Logger; import jpcsp.Emulator; /* * Tlzrc decompression. * * Based on tpunix implementation: * https://github.com/tpunix/kirk_engine/blob/master/npdpc/tlzrc.c */ public class Tlzrc { protected static Logger log = Emulator.log; private static class LzrcDecode { // input stream byte[] input; int in_ptr; int in_len; // output stream byte[] output; int out_ptr; int out_len; // range decode int range; int code; int lc; byte[] bm_values = new byte[8 * 256 + 8 * 39 + 18 * 8 + 8 * 8 + 8 * 31]; public int bm_literal(int i, int j) { return i * 256 + j; } public int bm_dist_bits(int i, int j) { return 8 * 256 + i * 39 + j; } public int bm_dist(int i, int j) { return 8 * 256 + 8 * 39 + i * 8 + j; } public int bm_match(int i, int j) { return 8 * 256 + 8 * 39 + 18 * 8 + i * 8 + j; } public int bm_len(int i, int j) { return 8 * 256 + 8 * 39 + 18 * 8 + 8 * 8 + i * 31 + j; } } private static int u8(byte[] a, int offset) { return a[offset] & 0xFF; } private static int rc_getbyte(LzrcDecode rc) { if (rc.in_ptr == rc.in_len) { log.error("End of input!"); return 0; } return u8(rc.input, rc.in_ptr++); } private static void rc_putbyte(LzrcDecode rc, byte b) { if (rc.out_ptr == rc.out_len) { log.error("Output overflow!"); return; } rc.output[rc.out_ptr++] = b; } private static void rc_init(LzrcDecode rc, byte[] out, int out_len, byte[] in, int in_len) { rc.input = in; rc.in_len = in_len; rc.in_ptr = 0; rc.output = out; rc.out_len = out_len; rc.out_ptr = 0; rc.range = -1; rc.lc = rc_getbyte(rc); rc.code = (rc_getbyte(rc) << 24) | (rc_getbyte(rc) << 16) | (rc_getbyte(rc) << 8) | rc_getbyte(rc); for (int i = 0; i < rc.bm_values.length; i++) { rc.bm_values[i] = (byte) 0x80; } } private static void normalize(LzrcDecode rc) { if (rc.range >= 0 && rc.range < 0x01000000) { rc.range <<= 8; rc.code = (rc.code << 8) | rc_getbyte(rc); } } // Decode a bit private static int rc_bit(LzrcDecode rc, byte[] probs, int offset) { normalize(rc); long bound = ((long) (rc.range >>> 8)) * u8(probs, offset); probs[offset] -= u8(probs, offset) >> 3; if ((rc.code & 0xFFFFFFFFL) < bound) { rc.range = (int) (bound & 0xFFFFFFFFL); probs[offset] += 31; return 1; } rc.code -= bound; rc.range -= bound; return 0; } // Decode a bittree starting from MSB private static int rc_bittree(LzrcDecode rc, byte[] probs, int offset, int limit) { int number = 1; do { number = (number << 1) | rc_bit(rc, probs, offset + number); } while (number < limit); return number; } // Decode a number // // A number is divided into three parts: // MSB 2 bits // direct bits (don't use probability model) // LSB 3 bits private static int rc_number(LzrcDecode rc, byte[] prob, int offset, int n) { int number = 1; if (n > 3) { number = (number << 1) | rc_bit(rc, prob, offset + 3); if (n > 4) { number = (number << 1) | rc_bit(rc, prob, offset + 3); if (n > 5) { // direct bits normalize(rc); for (int i = 0; i < n - 5; i++) { rc.range >>>= 1; number <<= 1; if ((rc.code & 0xFFFFFFFFL) < (rc.range & 0xFFFFFFFFL)) { number += 1; } else { rc.code -= rc.range; } } } } } if (n > 0) { number = (number << 1) | rc_bit(rc, prob, offset); if (n > 1) { number = (number << 1) | rc_bit(rc, prob, offset + 1); if (n > 2) { number = (number << 1) | rc_bit(rc, prob, offset + 2); } } } return number; } public static int lzrc_decompress(byte[] out, int out_len, byte[] in, int in_len) { LzrcDecode rc = new LzrcDecode(); rc_init(rc, out, out_len, in, in_len); if ((rc.lc & 0x80) != 0) { // Plain text System.arraycopy(rc.input, 5, rc.output, 0, rc.code); return rc.code; } int rc_state = 0; int last_byte = 0; while (true) { int match_step = 0; int bit = rc_bit(rc, rc.bm_values, rc.bm_match(rc_state, match_step)); if (bit == 0) { // 0 -> raw char if (rc_state > 0) { rc_state--; } int b = rc_bittree(rc, rc.bm_values, rc.bm_literal((last_byte >> rc.lc) & 0x07, 0), 0x100); b -= 0x100; rc_putbyte(rc, (byte) b); } else { // 1 -> a match // Find bits of match length int len_bits = 0; for (int i = 0; i < 7; i++) { match_step++; bit = rc_bit(rc, rc.bm_values, rc.bm_match(rc_state, match_step)); if (bit == 0) { break; } len_bits++; } // Find match length int match_len; if (len_bits == 0) { match_len = 1; } else { int len_state = ((len_bits - 1) << 2) + ((rc.out_ptr << (len_bits - 1)) & 0x03); match_len = rc_number(rc, rc.bm_values, rc.bm_len(rc_state, len_state), len_bits); if (match_len == 0xFF) { // End of stream return rc.out_ptr; } } // Find number of bits of match distance int dist_state = 0; int limit = 8; if (match_len > 2) { dist_state += 7; limit = 44; } int dist_bits = rc_bittree(rc, rc.bm_values, rc.bm_dist_bits(len_bits, dist_state), limit); dist_bits -= limit; // Find match distance int match_dist; if (dist_bits > 0) { match_dist = rc_number(rc, rc.bm_values, rc.bm_dist(dist_bits, 0), dist_bits); } else { match_dist = 1; } // Copy match bytes if (match_dist > rc.out_ptr || match_dist < 0) { log.error(String.format("match_dist out of range! 0x%08X", match_dist)); return -1; } int match_src = rc.out_ptr - match_dist; for (int i = 0; i < match_len + 1; i++) { rc_putbyte(rc, rc.output[match_src++]); } rc_state = 6 + ((rc.out_ptr + 1) & 1); } last_byte = rc.output[rc.out_ptr - 1]; } } }