diff options
author | Dabomstew <dbs.stew@gmail.com> | 2016-02-21 13:12:16 +1300 |
---|---|---|
committer | Dabomstew <dbs.stew@gmail.com> | 2016-02-21 13:12:16 +1300 |
commit | 40af2283b038f3b33b35f208b91a070a2744ddbd (patch) | |
tree | 15420d60e55351c445f5053fdef50761a137065a /src/compressors | |
parent | 76f2c51c92de206adf8220132ff81766fc06dee9 (diff) |
Working on Gen 1 mascot image, ugh that decompression.
Diffstat (limited to 'src/compressors')
-rw-r--r-- | src/compressors/DSDecmp.java | 195 | ||||
-rw-r--r-- | src/compressors/Gen1Decmp.java | 348 |
2 files changed, 543 insertions, 0 deletions
diff --git a/src/compressors/DSDecmp.java b/src/compressors/DSDecmp.java new file mode 100644 index 0000000..8f6442a --- /dev/null +++ b/src/compressors/DSDecmp.java @@ -0,0 +1,195 @@ +package compressors; + +import com.dabomstew.pkrandom.FileFunctions; + +//MODIFIED DSDECMP-JAVA SOURCE FOR RANDOMIZER'S NEEDS +//License is below + +//Copyright (c) 2010 Nick Kraayenbrink +// +//Permission is hereby granted, free of charge, to any person obtaining a copy +//of this software and associated documentation files (the "Software"), to deal +//in the Software without restriction, including without limitation the rights +//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//copies of the Software, and to permit persons to whom the Software is +//furnished to do so, subject to the following conditions: +// +//The above copyright notice and this permission notice shall be included in +//all copies or substantial portions of the Software. +// +//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +//THE SOFTWARE. + +public class DSDecmp { + + public static byte[] Decompress(byte[] data) { + return Decompress(data, 0); + } + + public static byte[] Decompress(byte[] data, int offset) { + switch (data[offset] & 0xFF) { + case 0x10: + return decompress10LZ(data, offset); + case 0x11: + return decompress11LZ(data, offset); + default: + return null; + } + } + + private static byte[] decompress10LZ(byte[] data, int offset) { + offset++; + int length = (data[offset] & 0xFF) | ((data[offset + 1] & 0xFF) << 8) + | ((data[offset + 2] & 0xFF) << 16); + offset += 3; + if (length == 0) { + length = FileFunctions.readFullInt(data, offset); + offset += 4; + } + + byte[] outData = new byte[length]; + int curr_size = 0; + int flags; + boolean flag; + int disp, n, b, cdest; + while (curr_size < outData.length) { + flags = data[offset++] & 0xFF; + for (int i = 0; i < 8; i++) { + flag = (flags & (0x80 >> i)) > 0; + if (flag) { + disp = 0; + b = data[offset++] & 0xFF; + n = b >> 4; + disp = (b & 0x0F) << 8; + disp |= data[offset++] & 0xFF; + n += 3; + cdest = curr_size; + if (disp > curr_size) + throw new ArrayIndexOutOfBoundsException( + "Cannot go back more than already written"); + for (int j = 0; j < n; j++) + outData[curr_size++] = outData[cdest - disp - 1 + j]; + + if (curr_size > outData.length) + break; + } else { + b = data[offset++] & 0xFF; + try { + outData[curr_size++] = (byte) b; + } catch (ArrayIndexOutOfBoundsException ex) { + if (b == 0) + break; + } + + if (curr_size > outData.length) + break; + } + } + } + return outData; + } + + private static byte[] decompress11LZ(byte[] data, int offset) { + offset++; + int length = (data[offset] & 0xFF) | ((data[offset + 1] & 0xFF) << 8) + | ((data[offset + 2] & 0xFF) << 16); + offset += 3; + if (length == 0) { + length = FileFunctions.readFullInt(data, offset); + offset += 4; + } + + byte[] outData = new byte[length]; + + int curr_size = 0; + int flags; + boolean flag; + int b1, bt, b2, b3, len, disp, cdest; + + while (curr_size < outData.length) { + flags = data[offset++] & 0xFF; + + for (int i = 0; i < 8 && curr_size < outData.length; i++) { + flag = (flags & (0x80 >> i)) > 0; + if (flag) { + b1 = data[offset++] & 0xFF; + + switch (b1 >> 4) { + case 0: + // ab cd ef + // => + // len = abc + 0x11 = bc + 0x11 + // disp = def + + len = b1 << 4; + bt = data[offset++] & 0xFF; + len |= bt >> 4; + len += 0x11; + + disp = (bt & 0x0F) << 8; + b2 = data[offset++] & 0xFF; + disp |= b2; + break; + + case 1: + // ab cd ef gh + // => + // len = bcde + 0x111 + // disp = fgh + // 10 04 92 3F => disp = 0x23F, len = 0x149 + 0x11 = + // 0x15A + bt = data[offset++] & 0xFF; + b2 = data[offset++] & 0xFF; + b3 = data[offset++] & 0xFF; + + len = (b1 & 0xF) << 12; // len = b000 + len |= bt << 4; // len = bcd0 + len |= (b2 >> 4); // len = bcde + len += 0x111; // len = bcde + 0x111 + disp = (b2 & 0x0F) << 8; // disp = f + disp |= b3; // disp = fgh + break; + + default: + // ab cd + // => + // len = a + threshold = a + 1 + // disp = bcd + + len = (b1 >> 4) + 1; + + disp = (b1 & 0x0F) << 8; + b2 = data[offset++] & 0xFF; + disp |= b2; + break; + } + + if (disp > curr_size) + throw new ArrayIndexOutOfBoundsException( + "Cannot go back more than already written"); + + cdest = curr_size; + + for (int j = 0; j < len && curr_size < outData.length; j++) + outData[curr_size++] = outData[cdest - disp - 1 + j]; + + if (curr_size > outData.length) + break; + } else { + outData[curr_size++] = data[offset++]; + + if (curr_size > outData.length) + break; + } + } + + } + return outData; + } + +} diff --git a/src/compressors/Gen1Decmp.java b/src/compressors/Gen1Decmp.java new file mode 100644 index 0000000..3f07d2a --- /dev/null +++ b/src/compressors/Gen1Decmp.java @@ -0,0 +1,348 @@ +package compressors; + +/** + * Pokemon Gen 1 sprite decompressor + * Source: https://github.com/pret/pokemon-reverse-engineering-tools/blob/master/pokemontools/pic.py + * (and gfx.py for flatten()) + * Ported to Java by Dabomstew + * + */ +public class Gen1Decmp { + + public static int bitflip(int x, int n) { + int r = 0; + while (n > 0) { + r = (r << 1) | (x & 1); + x >>= 1; + n -= 1; + } + return r; + } + + static int[] table1, table3; + static int[][] table2 = { + { 0x0, 0x1, 0x3, 0x2, 0x7, 0x6, 0x4, 0x5, 0xf, 0xe, 0xc, 0xd, 0x8, + 0x9, 0xb, 0xa }, + { 0xf, 0xe, 0xc, 0xd, 0x8, 0x9, 0xb, 0xa, 0x0, 0x1, 0x3, 0x2, 0x7, + 0x6, 0x4, 0x5 }, + { 0x0, 0x8, 0xc, 0x4, 0xe, 0x6, 0x2, 0xa, 0xf, 0x7, 0x3, 0xb, 0x1, + 0x9, 0xd, 0x5 }, + { 0xf, 0x7, 0x3, 0xb, 0x1, 0x9, 0xd, 0x5, 0x0, 0x8, 0xc, 0x4, 0xe, + 0x6, 0x2, 0xa }, }; + + static { + table1 = new int[16]; + table3 = new int[16]; + + for (int i = 0; i < 16; i++) { + table1[i] = (2 << i) - 1; + table3[i] = bitflip(i, 4); + } + } + + static int tilesize = 8; + + private BitStream bs; + private boolean mirror, planar; + private byte[] data; + private int sizex, sizey, size; + private int ramorder; + + public Gen1Decmp(byte[] input, int baseOffset) { + this(input, baseOffset, false, true); + } + + public Gen1Decmp(byte[] input, int baseOffset, boolean mirror, + boolean planar) { + this.bs = new BitStream(input, baseOffset); + this.mirror = mirror; + this.planar = planar; + this.data = null; + } + + public void decompress() { + byte[][] rams = new byte[2][]; + + this.sizex = this.readint(4) * tilesize; + this.sizey = this.readint(4); + + this.size = this.sizex * this.sizey; + + this.ramorder = this.readbit(); + + int r1 = this.ramorder; + int r2 = this.ramorder ^ 1; + + fillram(rams, r1); + int mode = this.readbit(); + if (mode == 1) { + mode += this.readbit(); + } + fillram(rams, r2); + + rams[0] = this.bitgroups_to_bytes(rams[0]); + rams[1] = this.bitgroups_to_bytes(rams[1]); + + if (mode == 0) { + this.decode(rams[0]); + this.decode(rams[1]); + } else if (mode == 1) { + this.decode(rams[r1]); + this.xor(rams[r1], rams[r2]); + } else if (mode == 2) { + this.decode(rams[r2], false); + this.decode(rams[r1]); + this.xor(rams[r1], rams[r2]); + } + + if (this.planar) { + data = new byte[this.size * 2]; + for (int i = 0; i < rams[0].length; i++) { + data[i * 2] = rams[0][i]; + data[i * 2 + 1] = rams[1][i]; + } + } else { + byte[] tmpdata = new byte[this.size * 8]; + BitStream r0S = new BitStream(rams[0]); + BitStream r1S = new BitStream(rams[1]); + for (int i = 0; i < tmpdata.length; i++) { + tmpdata[i] = (byte) (r0S.next() | (r1S.next() << 1)); + } + data = this.bitgroups_to_bytes(tmpdata); + } + + } + + public void transpose() { + if (data == null) { + return; + } + int tiles = data.length / 16; + int width = this.sizex / tilesize; + int height = this.sizey; + + byte[] newData = new byte[data.length]; + for (int tile = 0; tile < tiles; tile++) { + int oldTileX = tile % width; + int oldTileY = tile / width; + int newTileNum = oldTileX * height + oldTileY; + System.out.printf("tile %d => %d\n", tile, newTileNum); + System.arraycopy(data, tile * 16, newData, newTileNum * 16, 16); + } + data = newData; + + } + + public byte[] getData() { + return data; + } + + public byte[] getFlattenedData() { + return flatten(data); + } + + public int getWidth() { + return this.sizex; + } + + public int getHeight() { + return this.sizey * tilesize; + } + + private void fillram(byte[][] rams, int rOffset) { + int size = this.size * 4; + rams[rOffset] = new byte[size]; + boolean rleMode = this.readbit() == 0; + int written = 0; + while (written < size) { + if (rleMode) { + written += this.read_rle_chunk(rams[rOffset], written); + } else { + written += this.read_data_chunk(rams[rOffset], written, size); + } + rleMode = !rleMode; + } + rams[rOffset] = deinterlace_bitgroups(rams[rOffset]); + } + + private byte[] deinterlace_bitgroups(byte[] bits) { + byte[] l = new byte[bits.length]; + int offs = 0; + for (int y = 0; y < this.sizey; y++) { + for (int x = 0; x < this.sizex; x++) { + int i = 4 * y * this.sizex + x; + for (int j = 0; j < 4; j++) { + l[offs++] = bits[i]; + i += this.sizex; + } + } + } + return l; + } + + private int read_rle_chunk(byte[] ram, int baseOffset) { + int i = 0; + while (this.readbit() == 1) { + i++; + } + + int n = table1[i]; + int a = this.readint(i + 1); + n += a; + + for (i = 0; i < n; i++) { + ram[baseOffset + i] = 0; + } + return n; + } + + private int read_data_chunk(byte[] ram, int baseOffset, int size) { + int written = 0; + while (true) { + int bitgroup = this.readint(2); + if (bitgroup == 0) { + break; + } + ram[baseOffset + written] = (byte) bitgroup; + written++; + + if (baseOffset + written >= size) { + break; + } + } + return written; + } + + private int readbit() { + return bs.next(); + } + + private int readint(int count) { + return readint(bs, count); + } + + private int readint(BitStream strm, int count) { + int n = 0; + while (count > 0) { + n <<= 1; + n |= strm.next(); + count -= 1; + } + return n; + } + + private byte[] bitgroups_to_bytes(byte[] bits) { + int limiter = bits.length - 3; + byte[] ret = new byte[bits.length / 4]; + for (int i = 0; i < limiter; i += 4) { + int n = ((bits[i + 0] << 6) | (bits[i + 1] << 4) + | (bits[i + 2] << 2) | (bits[i + 3] << 0)); + ret[i / 4] = (byte) n; + } + return ret; + } + + private void decode(byte[] ram) { + decode(ram, this.mirror); + } + + private void decode(byte[] ram, boolean mirror) { + for (int x = 0; x < this.sizex; x++) { + int bit = 0; + for (int y = 0; y < this.sizey; y++) { + int i = y * this.sizex + x; + int a = ((ram[i] & 0xFF) >> 4) & 0x0F; + int b = ram[i] & 0x0F; + + a = table2[bit][a]; + bit = a & 1; + if (mirror) { + a = table3[a]; + } + + b = table2[bit][b]; + bit = b & 1; + if (mirror) { + b = table3[b]; + } + + ram[i] = (byte) ((a << 4) | b); + } + } + } + + private void xor(byte[] ram1, byte[] ram2) { + xor(ram1, ram2, this.mirror); + } + + private void xor(byte[] ram1, byte[] ram2, boolean mirror) { + for (int i = 0; i < ram2.length; i++) { + if (mirror) { + int a = ((ram2[i] & 0xFF) >> 4) & 0x0F; + int b = ram2[i] & 0x0F; + a = table3[a]; + b = table3[b]; + ram2[i] = (byte) ((a << 4) | b); + } + + ram2[i] = (byte) ((ram2[i] & 0xFF) ^ (ram1[i] & 0xFF)); + } + } + + private static class BitStream { + private byte[] data; + private int offset; + private int bitsLeft; + private int bufVal; + + private static final int bmask[] = { 0x00, 0x01, 0x03, 0x07, 0x0f, + 0x1f, 0x3f, 0x7f, 0xff, 0x1ff, 0x3ff, 0x7ff, 0xfff, 0x1fff, + 0x3fff, 0x7fff, 0xffff, 0x1ffff, 0x3ffff, 0x7ffff, 0xfffff, + 0x1fffff, 0x3fffff, 0x7fffff, 0xffffff, 0x1ffffff, 0x3ffffff, + 0x7ffffff, 0xfffffff, 0x1fffffff, 0x3fffffff, 0x7fffffff, + 0xffffffff }; + + public BitStream(byte[] data) { + this(data, 0); + } + + public BitStream(byte[] data, int baseOffset) { + this.data = data; + this.offset = baseOffset - 1; + this.bitsLeft = 0; + this.bufVal = -1; + } + + public int next() { + if (bitsLeft == 0) { + offset++; + bufVal = data[offset] & 0xFF; + if (offset >= data.length) { + return -1; + } + bitsLeft = 8; + } + + int retval = bufVal >> (bitsLeft - 1); + bufVal &= bmask[bitsLeft - 1]; + bitsLeft--; + + return retval; + } + } + + private static byte[] flatten(byte[] planar) { + byte[] strips = new byte[planar.length * 4]; + for (int j = 0; j < planar.length / 2; j++) { + int bottom = planar[j * 2] & 0xFF; + int top = planar[j * 2 + 1] & 0xFF; + byte[] strip = new byte[8]; + for (int i = 7; i >= 0; i--) { + strip[7 - i] = (byte) (((bottom >>> i) & 1) + ((top * 2 >>> i) & 2)); + } + System.arraycopy(strip, 0, strips, j * 8, 8); + } + return strips; + } + +} |