From d837d28b2fd7e1aecf20faa6ff07fa92a9b8b364 Mon Sep 17 00:00:00 2001 From: xIsm4 Date: Fri, 27 Jan 2023 14:14:16 +0100 Subject: [PATCH] Implement libdeflate compressor --- .../0040-Implement-libdeflate.patch | 2627 +++++++++++++++++ 1 file changed, 2627 insertions(+) create mode 100644 Waterfall-Proxy-Patches/0040-Implement-libdeflate.patch diff --git a/Waterfall-Proxy-Patches/0040-Implement-libdeflate.patch b/Waterfall-Proxy-Patches/0040-Implement-libdeflate.patch new file mode 100644 index 0000000..0d290e9 --- /dev/null +++ b/Waterfall-Proxy-Patches/0040-Implement-libdeflate.patch @@ -0,0 +1,2627 @@ +From c637708bcd56e66aa8da77b6bf67bed16348ae6c Mon Sep 17 00:00:00 2001 +From: xIsm4 +Date: Fri, 27 Jan 2023 14:12:44 +0100 +Subject: [PATCH] Implement libdeflate + + +diff --git a/flamecord/src/main/java/dev/_2lstudios/flamecord/configuration/FlameCordConfiguration.java b/flamecord/src/main/java/dev/_2lstudios/flamecord/configuration/FlameCordConfiguration.java +index 05f7da1d..1ef7f6a7 100644 +--- a/flamecord/src/main/java/dev/_2lstudios/flamecord/configuration/FlameCordConfiguration.java ++++ b/flamecord/src/main/java/dev/_2lstudios/flamecord/configuration/FlameCordConfiguration.java +@@ -60,6 +60,8 @@ public class FlameCordConfiguration extends FlameConfig { + @Getter + private int antibotFirewallExpire = 60; + @Getter ++ private int compressionLevel = 6; ++ @Getter + private boolean antibotFirewallLog = true; + @Getter + private boolean antibotFirewallIpset = true; +@@ -318,6 +320,7 @@ public class FlameCordConfiguration extends FlameConfig { + this.fakePlayersMode = setIfUnexistant("custom-motd.fakeplayers.mode", this.fakePlayersMode, configuration); + + this.tcpFastOpen = setIfUnexistant("tcp-fast-open", this.tcpFastOpen, configuration); ++ this.compressionLevel = setIfUnexistant("compression-level", this.compressionLevel, configuration); + + this.loggerInitialhandler = setIfUnexistant("logger.initialhandler", this.loggerInitialhandler, configuration); + this.loggerExceptions = setIfUnexistant("logger.exceptions", this.loggerExceptions, configuration); +diff --git a/native/compile-linux.sh b/native/compile-linux.sh +new file mode 100644 +index 00000000..5442dce9 +--- /dev/null ++++ b/native/compile-linux.sh +@@ -0,0 +1,25 @@ ++#!/bin/bash ++ ++if [ ! "$CC" ]; then ++ # The libdeflate authors recommend that we build using GCC as it produces "slightly faster binaries": ++ # https://github.com/ebiggers/libdeflate#for-unix ++ export CC=gcc ++fi ++ ++if [ ! -d libdeflate ]; then ++ echo "Cloning libdeflate..." ++ git clone https://github.com/ebiggers/libdeflate.git ++fi ++ ++echo "Compiling libdeflate..." ++cd libdeflate || exit ++CFLAGS="-fPIC -O2 -fomit-frame-pointer" make ++cd .. ++ ++CFLAGS="-O2 -I$JAVA_HOME/include/ -I$JAVA_HOME/include/linux/ -fPIC -shared -Wl,-z,noexecstack -Wall -Werror -fomit-frame-pointer" ++ARCH=$(uname -m) ++mkdir -p src/main/resources/linux_$ARCH ++$CC $CFLAGS -Ilibdeflate src/main/c/jni_util.c src/main/c/jni_zlib_deflate.c src/main/c/jni_zlib_inflate.c \ ++ libdeflate/libdeflate.a -o src/main/resources/linux_$ARCH/velocity-compress.so ++$CC $CFLAGS -shared src/main/c/jni_util.c src/main/c/jni_cipher_openssl.c \ ++ -o src/main/resources/linux_$ARCH/velocity-cipher.so -lcrypto +\ No newline at end of file +diff --git a/native/src/main/c/jni_cipher_macos.c b/native/src/main/c/jni_cipher_macos.c +new file mode 100644 +index 00000000..aa7d1aba +--- /dev/null ++++ b/native/src/main/c/jni_cipher_macos.c +@@ -0,0 +1,66 @@ ++#include ++#include ++#include ++#include ++#include "jni_util.h" ++ ++typedef unsigned char byte; ++ ++JNIEXPORT jlong JNICALL ++Java_com_velocitypowered_natives_encryption_OpenSslCipherImpl_init(JNIEnv *env, ++ jclass clazz, ++ jbyteArray key, ++ jboolean encrypt) ++{ ++ jsize keyLen = (*env)->GetArrayLength(env, key); ++ if (keyLen != 16) { ++ throwException(env, "java/lang/IllegalArgumentException", "cipher not 16 bytes"); ++ return 0; ++ } ++ ++ // Since we know the array size is always bounded, we can just use GetArrayRegion ++ // and save ourselves some error-checking headaches. ++ jbyte keyBytes[16]; ++ (*env)->GetByteArrayRegion(env, key, 0, keyLen, (jbyte*) keyBytes); ++ if ((*env)->ExceptionCheck(env)) { ++ return 0; ++ } ++ ++ CCCryptorRef cryptor = NULL; ++ CCCryptorStatus result = CCCryptorCreateWithMode(encrypt ? kCCEncrypt : kCCDecrypt, ++ kCCModeCFB8, ++ kCCAlgorithmAES128, ++ ccNoPadding, ++ keyBytes, ++ keyBytes, ++ 16, ++ NULL, ++ 0, ++ 0, ++ 0, ++ &cryptor); ++ if (result != kCCSuccess) { ++ throwException(env, "java/security/GeneralSecurityException", "openssl initialize cipher"); ++ return 0; ++ } ++ return (jlong) cryptor; ++} ++ ++JNIEXPORT void JNICALL ++Java_com_velocitypowered_natives_encryption_OpenSslCipherImpl_free(JNIEnv *env, ++ jclass clazz, ++ jlong ptr) ++{ ++ CCCryptorRelease((CCCryptorRef) ptr); ++} ++ ++JNIEXPORT void JNICALL ++Java_com_velocitypowered_natives_encryption_OpenSslCipherImpl_process(JNIEnv *env, ++ jclass clazz, ++ jlong ptr, ++ jlong source, ++ jint len, ++ jlong dest) ++{ ++ CCCryptorUpdate((CCCryptorRef) ptr, (byte*) source, len, (byte*) dest, len, NULL); ++} +\ No newline at end of file +diff --git a/native/src/main/c/jni_cipher_openssl.c b/native/src/main/c/jni_cipher_openssl.c +new file mode 100644 +index 00000000..83515be5 +--- /dev/null ++++ b/native/src/main/c/jni_cipher_openssl.c +@@ -0,0 +1,62 @@ ++#include ++#include ++#include ++#include ++#include "jni_util.h" ++ ++typedef unsigned char byte; ++ ++JNIEXPORT jlong JNICALL ++Java_com_velocitypowered_natives_encryption_OpenSslCipherImpl_init(JNIEnv *env, ++ jclass clazz, ++ jbyteArray key, ++ jboolean encrypt) ++{ ++ jsize keyLen = (*env)->GetArrayLength(env, key); ++ if (keyLen != 16) { ++ throwException(env, "java/lang/IllegalArgumentException", "cipher not 16 bytes"); ++ return 0; ++ } ++ ++ EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); ++ if (ctx == NULL) { ++ throwException(env, "java/lang/OutOfMemoryError", "allocate cipher"); ++ return 0; ++ } ++ ++ // Since we know the array size is always bounded, we can just use GetArrayRegion ++ // and save ourselves some error-checking headaches. ++ jbyte keyBytes[16]; ++ (*env)->GetByteArrayRegion(env, key, 0, keyLen, (jbyte*) keyBytes); ++ if ((*env)->ExceptionCheck(env)) { ++ return 0; ++ } ++ ++ int result = EVP_CipherInit(ctx, EVP_aes_128_cfb8(), (byte*) keyBytes, (byte*) keyBytes, ++ encrypt); ++ if (result != 1) { ++ EVP_CIPHER_CTX_free(ctx); ++ throwException(env, "java/security/GeneralSecurityException", "openssl initialize cipher"); ++ return 0; ++ } ++ return (jlong) ctx; ++} ++ ++JNIEXPORT void JNICALL ++Java_com_velocitypowered_natives_encryption_OpenSslCipherImpl_free(JNIEnv *env, ++ jclass clazz, ++ jlong ptr) ++{ ++ EVP_CIPHER_CTX_free((EVP_CIPHER_CTX *) ptr); ++} ++ ++JNIEXPORT void JNICALL ++Java_com_velocitypowered_natives_encryption_OpenSslCipherImpl_process(JNIEnv *env, ++ jclass clazz, ++ jlong ptr, ++ jlong source, ++ jint len, ++ jlong dest) ++{ ++ EVP_CipherUpdate((EVP_CIPHER_CTX*) ptr, (byte*) dest, &len, (byte*) source, len); ++} +\ No newline at end of file +diff --git a/native/src/main/c/jni_util.c b/native/src/main/c/jni_util.c +new file mode 100644 +index 00000000..1e2b6bd8 +--- /dev/null ++++ b/native/src/main/c/jni_util.c +@@ -0,0 +1,12 @@ ++#include ++#include "jni_util.h" ++ ++void JNICALL ++throwException(JNIEnv *env, const char *type, const char *msg) ++{ ++ jclass klazz = (*env)->FindClass(env, type); ++ ++ if (klazz != 0) { ++ (*env)->ThrowNew(env, klazz, msg); ++ } ++} +\ No newline at end of file +diff --git a/native/src/main/c/jni_util.h b/native/src/main/c/jni_util.h +new file mode 100644 +index 00000000..8938b26c +--- /dev/null ++++ b/native/src/main/c/jni_util.h +@@ -0,0 +1,4 @@ ++#include ++ ++JNIEXPORT void JNICALL ++throwException(JNIEnv *env, const char *type, const char *msg); +\ No newline at end of file +diff --git a/native/src/main/c/jni_zlib_deflate.c b/native/src/main/c/jni_zlib_deflate.c +new file mode 100644 +index 00000000..809a7f85 +--- /dev/null ++++ b/native/src/main/c/jni_zlib_deflate.c +@@ -0,0 +1,43 @@ ++#include ++#include ++#include ++#include ++#include ++#include "jni_util.h" ++ ++JNIEXPORT jlong JNICALL ++Java_com_velocitypowered_natives_compression_NativeZlibDeflate_init(JNIEnv *env, ++ jclass clazz, ++ jint level) ++{ ++ struct libdeflate_compressor *compressor = libdeflate_alloc_compressor(level); ++ if (compressor == NULL) { ++ // Out of memory! ++ throwException(env, "java/lang/OutOfMemoryError", "libdeflate allocate compressor"); ++ return 0; ++ } ++ return (jlong) compressor; ++} ++ ++JNIEXPORT void JNICALL ++Java_com_velocitypowered_natives_compression_NativeZlibDeflate_free(JNIEnv *env, ++ jclass clazz, ++ jlong ctx) ++{ ++ libdeflate_free_compressor((struct libdeflate_compressor *) ctx); ++} ++ ++JNIEXPORT jlong JNICALL ++Java_com_velocitypowered_natives_compression_NativeZlibDeflate_process(JNIEnv *env, ++ jclass clazz, ++ jlong ctx, ++ jlong sourceAddress, ++ jint sourceLength, ++ jlong destinationAddress, ++ jint destinationLength) ++{ ++ struct libdeflate_compressor *compressor = (struct libdeflate_compressor *) ctx; ++ size_t produced = libdeflate_zlib_compress(compressor, (void *) sourceAddress, sourceLength, ++ (void *) destinationAddress, destinationLength); ++ return (jlong) produced; ++} +\ No newline at end of file +diff --git a/native/src/main/c/jni_zlib_inflate.c b/native/src/main/c/jni_zlib_inflate.c +new file mode 100644 +index 00000000..d9131908 +--- /dev/null ++++ b/native/src/main/c/jni_zlib_inflate.c +@@ -0,0 +1,61 @@ ++#include ++#include ++#include ++#include ++#include ++#include "jni_util.h" ++ ++JNIEXPORT jlong JNICALL ++Java_com_velocitypowered_natives_compression_NativeZlibInflate_init(JNIEnv *env, ++ jclass clazz) ++{ ++ struct libdeflate_decompressor *decompress = libdeflate_alloc_decompressor(); ++ if (decompress == NULL) { ++ // Out of memory! ++ throwException(env, "java/lang/OutOfMemoryError", "libdeflate allocate decompressor"); ++ return 0; ++ } ++ ++ return (jlong) decompress; ++} ++ ++JNIEXPORT void JNICALL ++Java_com_velocitypowered_natives_compression_NativeZlibInflate_free(JNIEnv *env, ++ jclass clazz, ++ jlong ctx) ++{ ++ libdeflate_free_decompressor((struct libdeflate_decompressor *) ctx); ++} ++ ++JNIEXPORT jboolean JNICALL ++Java_com_velocitypowered_natives_compression_NativeZlibInflate_process(JNIEnv *env, ++ jclass clazz, ++ jlong ctx, ++ jlong sourceAddress, ++ jint sourceLength, ++ jlong destinationAddress, ++ jint destinationLength, ++ jlong maximumSize) ++{ ++ struct libdeflate_decompressor *decompress = (struct libdeflate_decompressor *) ctx; ++ enum libdeflate_result result = libdeflate_zlib_decompress(decompress, (void *) sourceAddress, ++ sourceLength, (void *) destinationAddress, destinationLength, NULL); ++ ++ switch (result) { ++ case LIBDEFLATE_SUCCESS: ++ // We are happy ++ return JNI_TRUE; ++ case LIBDEFLATE_BAD_DATA: ++ throwException(env, "java/util/zip/DataFormatException", "inflate data is bad"); ++ return JNI_FALSE; ++ case LIBDEFLATE_SHORT_OUTPUT: ++ case LIBDEFLATE_INSUFFICIENT_SPACE: ++ // These cases are the same for us. We expect the full uncompressed size to be known. ++ throwException(env, "java/util/zip/DataFormatException", "uncompressed size is inaccurate"); ++ return JNI_FALSE; ++ default: ++ // Unhandled case ++ throwException(env, "java/util/zip/DataFormatException", "unknown libdeflate return code"); ++ return JNI_FALSE; ++ } ++} +\ No newline at end of file +diff --git a/native/src/main/java/com/velocitypowered/natives/compression/NativeZlibDeflate.java b/native/src/main/java/com/velocitypowered/natives/compression/NativeZlibDeflate.java +new file mode 100644 +index 00000000..4ba41fc1 +--- /dev/null ++++ b/native/src/main/java/com/velocitypowered/natives/compression/NativeZlibDeflate.java +@@ -0,0 +1,15 @@ ++package com.velocitypowered.natives.compression; ++ ++/** ++ * Represents a native interface for zlib's deflate functions. ++ */ ++ ++//TODO Need refactor and recompile native! ++public class NativeZlibDeflate { ++ ++ public static native long init(int level); ++ ++ public static native long free(long ctx); ++ ++ public static native int process(long ctx, long sourceAddress, int sourceLength, long destinationAddress, int destinationLength); ++} +diff --git a/native/src/main/java/com/velocitypowered/natives/compression/NativeZlibInflate.java b/native/src/main/java/com/velocitypowered/natives/compression/NativeZlibInflate.java +new file mode 100644 +index 00000000..81d92e75 +--- /dev/null ++++ b/native/src/main/java/com/velocitypowered/natives/compression/NativeZlibInflate.java +@@ -0,0 +1,17 @@ ++package com.velocitypowered.natives.compression; ++ ++import java.util.zip.DataFormatException; ++ ++/** ++ * Represents a native interface for zlib's inflate functions. ++ */ ++ ++//TODO Need refactor and recompile native! ++public class NativeZlibInflate { ++ ++ public static native long init(); ++ ++ public static native long free(long ctx); ++ ++ public static native boolean process(long ctx, long sourceAddress, int sourceLength, long destinationAddress, int destinationLength) throws DataFormatException; ++} +diff --git a/native/src/main/java/dev/_2lstudios/flamecord/natives/MoreByteBufUtils.java b/native/src/main/java/dev/_2lstudios/flamecord/natives/MoreByteBufUtils.java +new file mode 100644 +index 00000000..a16a2584 +--- /dev/null ++++ b/native/src/main/java/dev/_2lstudios/flamecord/natives/MoreByteBufUtils.java +@@ -0,0 +1,28 @@ ++package dev._2lstudios.flamecord.natives; ++ ++import dev._2lstudios.flamecord.natives.compress.Compressor; ++import io.netty.buffer.ByteBuf; ++import io.netty.buffer.ByteBufAllocator; ++ ++public class MoreByteBufUtils { ++ ++ public static ByteBuf ensureCompatible(ByteBufAllocator alloc, Compressor nativeStuff, ByteBuf buf) { ++ if (isCompatible(nativeStuff, buf)) { ++ return buf.retain(); ++ } ++ ++ // It's not, so we must make a direct copy. ++ ByteBuf newBuf = alloc.directBuffer(buf.readableBytes()); ++ newBuf.writeBytes(buf); ++ return newBuf; ++ } ++ ++ private static boolean isCompatible(Compressor nativeStuff, ByteBuf buf) { ++ if (nativeStuff.isNeedDirectBuffer()) { ++ return buf.hasMemoryAddress(); ++ } ++ ++ return true; ++ } ++ ++} +\ No newline at end of file +diff --git a/native/src/main/java/dev/_2lstudios/flamecord/natives/NativeEnvironmentDetector.java b/native/src/main/java/dev/_2lstudios/flamecord/natives/NativeEnvironmentDetector.java +new file mode 100644 +index 00000000..50f042bd +--- /dev/null ++++ b/native/src/main/java/dev/_2lstudios/flamecord/natives/NativeEnvironmentDetector.java +@@ -0,0 +1,36 @@ ++package dev._2lstudios.flamecord.natives; ++ ++import io.netty.buffer.ByteBuf; ++import io.netty.buffer.Unpooled; ++ ++public final class NativeEnvironmentDetector { ++ ++ private static final boolean IS_AMD64; ++ private static final boolean IS_AARCH64; ++ private static final boolean CAN_GET_MEMORYADDRESS; ++ ++ static { ++ ByteBuf test = Unpooled.directBuffer(); ++ try { ++ CAN_GET_MEMORYADDRESS = test.hasMemoryAddress(); ++ } ++ finally { ++ test.release(); ++ } ++ ++ String osArch = System.getProperty("os.arch", ""); ++ // HotSpot on Intel macOS prefers x86_64, but OpenJ9 on macOS and HotSpot/OpenJ9 elsewhere ++ // give amd64. ++ IS_AMD64 = osArch.equals("amd64") || osArch.equals("x86_64"); ++ IS_AARCH64 = osArch.equals("aarch64") || osArch.equals("arm64"); ++ } ++ ++ public static boolean isLinux_X86_64() { ++ return CAN_GET_MEMORYADDRESS && System.getProperty("os.name", "").equalsIgnoreCase("Linux") && IS_AMD64; ++ } ++ ++ public static boolean isLinux_AARCH64() { ++ return CAN_GET_MEMORYADDRESS && System.getProperty("os.name", "").equalsIgnoreCase("Linux") && IS_AARCH64; ++ } ++ ++} +\ No newline at end of file +diff --git a/native/src/main/java/dev/_2lstudios/flamecord/natives/Natives.java b/native/src/main/java/dev/_2lstudios/flamecord/natives/Natives.java +new file mode 100644 +index 00000000..a6709a86 +--- /dev/null ++++ b/native/src/main/java/dev/_2lstudios/flamecord/natives/Natives.java +@@ -0,0 +1,138 @@ ++package dev._2lstudios.flamecord.natives; ++ ++import dev._2lstudios.flamecord.natives.compress.Compressor; ++import dev._2lstudios.flamecord.natives.compress.CompressorFactory; ++import dev._2lstudios.flamecord.natives.compress.JavaCompressor; ++import dev._2lstudios.flamecord.natives.compress.LibdeflateCompressor; ++ ++import java.io.IOException; ++import java.io.InputStream; ++import java.nio.file.Files; ++import java.nio.file.Path; ++import java.nio.file.StandardCopyOption; ++import java.util.Arrays; ++import java.util.List; ++ ++public class Natives { ++ ++ private static final CompressorFactory COMPRESSOR_FACTORY = loadAvailableCompressFactory(); ++ ++ public static CompressorFactory getCompressorFactory() { ++ return COMPRESSOR_FACTORY; ++ } ++ ++ public static List getAvailableCompressorFactories() { ++ return Arrays.asList(new CompressorFactory() { ++ @Override ++ public String getName() { ++ return "Libdeflate (linux_x86_64)"; ++ } ++ ++ @Override ++ public boolean isCorrectEnvironment() { ++ return NativeEnvironmentDetector.isLinux_X86_64(); ++ } ++ ++ @Override ++ public String getNativePath() { ++ return "/libdeflate_x86_64.so"; ++ } ++ ++ @Override ++ public Compressor create(int level) { ++ return new LibdeflateCompressor(level); ++ } ++ }, new CompressorFactory() { ++ @Override ++ public String getName() { ++ return "Libdeflate (linux_aarch64)"; ++ } ++ ++ @Override ++ public boolean isCorrectEnvironment() { ++ return NativeEnvironmentDetector.isLinux_AARCH64(); ++ } ++ ++ @Override ++ public String getNativePath() { ++ return "/libdeflate_aarch64.so"; ++ } ++ ++ @Override ++ public Compressor create(int level) { ++ return new LibdeflateCompressor(level); ++ } ++ }, new CompressorFactory() { ++ @Override ++ public String getName() { ++ return "Java"; ++ } ++ ++ @Override ++ public boolean isCorrectEnvironment() { ++ return true; ++ } ++ ++ @Override ++ public String getNativePath() { ++ return null; ++ } ++ ++ @Override ++ public Compressor create(int level) { ++ return new JavaCompressor(level); ++ } ++ }); ++ } ++ ++ public static CompressorFactory loadAvailableCompressFactory() { ++ for (CompressorFactory factory : getAvailableCompressorFactories()) { ++ if (factory.isCorrectEnvironment()) { ++ String nativePath = factory.getNativePath(); ++ if (nativePath != null) { ++ try { ++ copyAndLoadNative(nativePath); ++ } ++ catch (Exception ignored) { ++ continue; ++ } ++ } ++ ++ return factory; ++ } ++ } ++ ++ throw new IllegalStateException("None of the compress factories recognized the environment!"); ++ } ++ ++ //Too good method to rewrite ++ private static void copyAndLoadNative(String path) { ++ try { ++ InputStream nativeLib = Natives.class.getResourceAsStream(path); ++ if (nativeLib == null) { ++ throw new IllegalStateException("Native library " + path + " not found."); ++ } ++ ++ Path tempFile = Files.createTempFile("native-", path.substring(path.lastIndexOf('.'))); ++ Files.copy(nativeLib, tempFile, StandardCopyOption.REPLACE_EXISTING); ++ Runtime.getRuntime().addShutdownHook(new Thread(() -> { ++ try { ++ Files.deleteIfExists(tempFile); ++ } ++ catch (IOException ignored) { ++ // Well, it doesn't matter... ++ } ++ })); ++ ++ try { ++ System.load(tempFile.toAbsolutePath().toString()); ++ } ++ catch (UnsatisfiedLinkError e) { ++ throw new RuntimeException("Unable to load native " + tempFile.toAbsolutePath(), e); ++ } ++ } ++ catch (IOException e) { ++ throw new RuntimeException("Unable to copy natives", e); ++ } ++ } ++} +\ No newline at end of file +diff --git a/native/src/main/java/dev/_2lstudios/flamecord/natives/compress/Compressor.java b/native/src/main/java/dev/_2lstudios/flamecord/natives/compress/Compressor.java +new file mode 100644 +index 00000000..f28ae314 +--- /dev/null ++++ b/native/src/main/java/dev/_2lstudios/flamecord/natives/compress/Compressor.java +@@ -0,0 +1,16 @@ ++package dev._2lstudios.flamecord.natives.compress; ++ ++import io.netty.buffer.ByteBuf; ++ ++import java.io.Closeable; ++import java.util.zip.DataFormatException; ++ ++public interface Compressor extends Closeable { ++ ++ void inflate(ByteBuf source, ByteBuf destination, int uncompressedSize) throws DataFormatException; ++ ++ void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException; ++ ++ boolean isNeedDirectBuffer(); ++ ++} +\ No newline at end of file +diff --git a/native/src/main/java/dev/_2lstudios/flamecord/natives/compress/CompressorFactory.java b/native/src/main/java/dev/_2lstudios/flamecord/natives/compress/CompressorFactory.java +new file mode 100644 +index 00000000..3aed690a +--- /dev/null ++++ b/native/src/main/java/dev/_2lstudios/flamecord/natives/compress/CompressorFactory.java +@@ -0,0 +1,13 @@ ++package dev._2lstudios.flamecord.natives.compress; ++ ++public interface CompressorFactory { ++ ++ String getName(); ++ ++ boolean isCorrectEnvironment(); ++ ++ String getNativePath(); ++ ++ Compressor create(int level); ++ ++} +\ No newline at end of file +diff --git a/native/src/main/java/dev/_2lstudios/flamecord/natives/compress/JavaCompressor.java b/native/src/main/java/dev/_2lstudios/flamecord/natives/compress/JavaCompressor.java +new file mode 100644 +index 00000000..7d99eb02 +--- /dev/null ++++ b/native/src/main/java/dev/_2lstudios/flamecord/natives/compress/JavaCompressor.java +@@ -0,0 +1,90 @@ ++package dev._2lstudios.flamecord.natives.compress; ++ ++import io.netty.buffer.ByteBuf; ++ ++import java.util.zip.DataFormatException; ++import java.util.zip.Deflater; ++import java.util.zip.Inflater; ++ ++import static com.google.common.base.Preconditions.checkArgument; ++import static com.google.common.base.Preconditions.checkState; ++ ++public class JavaCompressor implements Compressor { ++ ++ private final byte[] buffer = new byte[8192]; //NullCordX ++ ++ private final Deflater deflater; ++ private final Inflater inflater; ++ private boolean disposed = false; ++ ++ public JavaCompressor(int level) { ++ this.deflater = new Deflater(level); ++ this.inflater = new Inflater(); ++ } ++ ++ @Override ++ public void inflate(ByteBuf source, ByteBuf destination, int uncompressedSize) throws DataFormatException { ++ ensureNotDisposed(); ++ ++ // We (probably) can't nicely deal with >=1 buffer nicely, so let's scream loudly. ++ checkArgument(source.nioBufferCount() == 1, "source has multiple backing buffers"); ++ checkArgument(destination.nioBufferCount() == 1, "destination has multiple backing buffers"); ++ ++ try { ++ byte[] inData = new byte[source.readableBytes()]; ++ source.readBytes(inData); ++ ++ this.inflater.setInput(inData); ++ ++ while (!this.inflater.finished() && this.inflater.getTotalIn() < inData.length) { ++ int count = this.inflater.inflate(this.buffer); ++ destination.writeBytes(this.buffer, 0, count); ++ } ++ ++ } ++ finally { ++ this.inflater.reset(); ++ } ++ } ++ ++ @Override ++ public void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException { ++ ensureNotDisposed(); ++ // We (probably) can't nicely deal with >=1 buffer nicely, so let's scream loudly. ++ checkArgument(source.nioBufferCount() == 1, "source has multiple backing buffers"); ++ checkArgument(destination.nioBufferCount() == 1, "destination has multiple backing buffers"); ++ ++ try { ++ byte[] inData = new byte[source.readableBytes()]; ++ source.readBytes(inData); ++ ++ this.deflater.setInput(inData); ++ this.deflater.finish(); ++ ++ while (!this.deflater.finished()) { ++ int count = this.deflater.deflate(this.buffer); ++ destination.writeBytes(this.buffer, 0, count); ++ } ++ } ++ finally { ++ this.deflater.reset(); ++ } ++ } ++ ++ @Override ++ public void close() { ++ this.disposed = true; ++ this.deflater.end(); ++ this.inflater.end(); ++ } ++ ++ private void ensureNotDisposed() { ++ checkState(!this.disposed, "Object already disposed"); ++ } ++ ++ @Override ++ public boolean isNeedDirectBuffer() { ++ return false; ++ } ++ ++} +diff --git a/native/src/main/java/dev/_2lstudios/flamecord/natives/compress/LibdeflateCompressor.java b/native/src/main/java/dev/_2lstudios/flamecord/natives/compress/LibdeflateCompressor.java +new file mode 100644 +index 00000000..fd9f9007 +--- /dev/null ++++ b/native/src/main/java/dev/_2lstudios/flamecord/natives/compress/LibdeflateCompressor.java +@@ -0,0 +1,83 @@ ++package dev._2lstudios.flamecord.natives.compress; ++ ++import com.google.common.base.Preconditions; ++import com.velocitypowered.natives.compression.NativeZlibDeflate; ++import com.velocitypowered.natives.compression.NativeZlibInflate; ++import io.netty.buffer.ByteBuf; ++ ++import java.util.zip.DataFormatException; ++ ++public class LibdeflateCompressor implements Compressor { ++ ++ private final long inflateCtx; ++ private final long deflateCtx; ++ private boolean disposed = false; ++ ++ public LibdeflateCompressor(int level) { ++ int correctedLevel = level == -1 ? 6 : level; ++ if (correctedLevel > 12 || correctedLevel < 1) { ++ throw new IllegalArgumentException("Invalid compression level " + level); ++ } ++ ++ this.inflateCtx = NativeZlibInflate.init(); ++ this.deflateCtx = NativeZlibDeflate.init(correctedLevel); ++ } ++ ++ @Override ++ public void inflate(ByteBuf source, ByteBuf destination, int uncompressedSize) ++ throws DataFormatException { ++ ensureNotDisposed(); ++ ++ destination.ensureWritable(uncompressedSize); ++ ++ long sourceAddress = source.memoryAddress() + source.readerIndex(); ++ long destinationAddress = destination.memoryAddress() + destination.writerIndex(); ++ ++ NativeZlibInflate.process(this.inflateCtx, sourceAddress, source.readableBytes(), destinationAddress, ++ uncompressedSize); ++ destination.writerIndex(destination.writerIndex() + uncompressedSize); ++ } ++ ++ @Override ++ public void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException { ++ ensureNotDisposed(); ++ ++ while (true) { ++ long sourceAddress = source.memoryAddress() + source.readerIndex(); ++ long destinationAddress = destination.memoryAddress() + destination.writerIndex(); ++ ++ int produced = NativeZlibDeflate.process(this.deflateCtx, sourceAddress, source.readableBytes(), ++ destinationAddress, destination.writableBytes()); ++ if (produced > 0) { ++ destination.writerIndex(destination.writerIndex() + produced); ++ break; ++ } ++ else if (produced == 0) { ++ // Insufficient room - enlarge the buffer. ++ destination.capacity(destination.capacity() * 2); ++ } ++ else { ++ throw new DataFormatException("libdeflate returned unknown code " + produced); ++ } ++ } ++ } ++ ++ private void ensureNotDisposed() { ++ Preconditions.checkState(!this.disposed, "Object already disposed"); ++ } ++ ++ @Override ++ public void close() { ++ if (!this.disposed) { ++ NativeZlibInflate.free(this.inflateCtx); ++ NativeZlibDeflate.free(this.deflateCtx); ++ } ++ this.disposed = true; ++ } ++ ++ @Override ++ public boolean isNeedDirectBuffer() { ++ return true; ++ } ++ ++} +diff --git a/native/src/main/resources/libdeflate_aarch64.so b/native/src/main/resources/libdeflate_aarch64.so +new file mode 100644 +index 0000000000000000000000000000000000000000..284450dd1c3632601b58628619dabee4ea57fa91 +GIT binary patch +literal 74880 +zcmb@v3w%`7x%a>J-jigK3FN|6Kr)j=l<}@ug4Jecf)El#Nz#8k+H-Of4Ge-2-_w9- +zCKoL@s7&P?d#G=dfG8PDYcHOYoWB2(fTcogtKsx^&Up_r3AZp<5e-W=$fVy{|xmn>eDNw%=iQI@=x!Ysz9ai(uf=XA&MIEJ)RxQ<`Z)GH^l_~I#QEEL?0!f5oOn+z<-L*kdn57p66fAH +znTfMKR*kZ8mgBAX^NY{<@p6f?l$C$?9DeKp`R(kqEUVeF;y+Q#GP5<)iXTSX|iyV0_ +zZOMCbqj^!1Q&V%Caxa@>636;=>zm7-UGw~Z)(k9CZ(jHG-Pd3HwDaTH;RAuI!#w`N +ze}+m`SEYXbv7sibYIR{CtpcfBN_!XooAL$sg+AxSg}Gk9k$Z{znO{&(&KKO*zu^9@ +zUvOXe=e$I_N4a;Ye&v5bJ=c6eJ+r@{{8z2|9m?s5HGWU*q<#uP*~+1ANRQokJ%0ZL +zXSv@SFFzxG|K@o4Q2c&D{QfuM_nq;268f_vUeD|C@`?Ko#P2^6zkgf29#vKK(8H@% +zR;_-l=F!Khs#MjY%Dbu_So-MFhnB5=Z0VzwcNMQ#wQ^}?P3?-Mv1gZlQdQSjQ}y7o +zl{G7ttzD`ftABLWH|}VxTl&aj%T}#ar8R$FQ&qR>;i|u1x?)w`vd7juvg#YO@Ickd +zn#Y#?{nFL)?2$*8u3k;ys>Rm5`&KNgoxk+K6*Z47ty;En*<*ig9S=Ubbm?DP$0Luf +zs)NY?ztt>S`PX+RQO94?okSgp?mWEo;nhnYQ=IA^S)(4VS-}{|)x)d)eyLjZ$kLVS +z(WNyHASX*7eRSojDxNYzjB4GARjZdO#;NYQ)vK=il3IA@B2RJEP1oHxHz8U&wnXDd +z{1^Kz|JOeApOlw7Cb}f{L;k%O_(#m1u1dKl{}TSjh#T(AxEvpg`<2A~-n3Zx_b$NJB=#@x3IcC5Ji{h0f460IkpG55#E +z%gQNXZ^gBzjJaPQr$_F`RXqMQMz0P%;v*9O?D6t)Kh7fSQRbNY3Es+ssblVsrQbQ` +z{#bgh8goBkQ>307#@rw4QVPc0j}M6ySLT@eWBF4u=Kk3J-ZSR@IID)(|Lz@gKV!`O +z!aUtjO&3YFUY@#B3vc5$!QsQT@gV(NX$ysy;zS)VK%%^WNYIQ5+0<~WVt +zB#ul?ZOYQkQT&tj%(_nII(N)<8rPmN*WZa +zI+tr*`?UR8T<4Ctc603+bA27xwXy3EeN!hEj+#pS_+s(yqVg`K`g6}W9Vkj~_Pfp(z0gPbLEiPezi_nH +zdRM>juCx~$m!DbVa>k)XCs1GYV9`8NQ@2>O9$Gl+<2~zl@7%!mqR|(B$Kh1I^6Eit +z-Wi8E`tjqtFN|eXzVVy?pkIkTovHX)*I7dko-tHVSLUc^syX@z?U<_Yx3uw-I>s{0 +zvrnjJ(3ltu854qMDt7OvD=m3z7R4}f=SSum!mf4*i-|? +zq!RwPh2P6GRq3*;CCG%ggx{a(uQN5^^4SAsjE3?{@}whHW^TNU@Z_^~*sVIQk3T=j +z^D`Acg5OE*v(WX?YFpPw@O`UIWqokJ*7ZTF7Vw;X^1z4IcuWeOtJAyAA)6yca!`Zb +zQLoYUC;B)_pFg=;jn1atBgS<1pfM#F)|36hpW=yTLC4yW92bp +zg1cjFeK-1Wib>UfcG&*NC?aX!^nD&#&$a$UFQ)o=^&U@j7Y5%0F +zo~UEu?6VJ6dxmN>-HF{(1KilUG4-2PH74LWMZXCcDJi35uXh?nPoP%;F>*|z4x^kdf6S1*L2tB?Vh&4 +zrj~41hU4e=&FS8&CZ@IP%3JhFv{LH#AV<=Fz3IsVVPkTzSJ(YQ$19&iU!3}QPKA__ +z{y1s-gZqoR&eeIkM!m&dAFZC>^~u$um%p-fZ%e%V*85g>5B@27i_|CeBJ@@3DWPP^+5(;m_dv(v^Slg4OQ_A7EUw +z{* +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 +zZ}~3^TH>=X`oS$nylo;h~&!hQdxx@C!ae +zp7fKNnJRF`n9xs5>K~+iC*!Z<9}@TivNn+TEp7HfpR}RyBd#&+2tFgvrM+UG7BFv6 +zqct`)nz)~!_b|41KDJBcNA@Ep4LRax56VG +zTEd1cD7 +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 +z7w;w8hY&CxT9y|5?jELHz|-zdnp_( +z%O32m;n<73=ud3@j_Zle?-XN;XtUq7sTDj!^B?SpVZ6o@UEis-8UEUI-S2sS!GU7V +zP4<+?Zq^brUJkJvVl%{cXxI%|7bv#FRK2$|cfMHJzOu^1hE!ogYS8CoY)A)t0FK4& +zNGe#9*pdC4RtVk~=Wm)rFrZ#+ih^e~U?vKi;#^c+K>Q)NGI{`SDO24Tw-?wKiBrGG +zzBJ3cX57Y%eSfUI>Er!F_{OSC)v$rwr7B-F>>#GhnW!4J!Z&=+maX#Kp?jsB(3S05 +zSkvjRwdwvl+P*fgi)TmKvrx=+lvu_NkH*?n;S2LVkrrYo;gw}yFYu{wRl?RL_;fLk +zgg?9ClgQ0heo{zP^(m%2N3H$3o_N1;>aDG{r +zSBL$riN7~i?li`Yxu7W`o6_$=*4vn>>3~Nya(vo<)`(20ratjm_wrlt`!A9Y5B)YP +zU3V+yGh%Xb`vp01(OxQY@{0XaIZ2Dli8C-pPAoY{Mh?K`1HuR0UyZEH692<9bJ2q_ +zd4Z0YycnO67tzDRWB9TCfka*+P +z)3LbW3^);M7aIoSWycGj; +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 +z>*tV#eB@v}HasRPNj)D*Cf5CI$cOE} +zqI1L!i479lBl6F9Ejb9C;ZRjUD`S5Q8T@Pfwv4;wuPy&J9{-o>-Y}8*GUR2Dw#B!V +z>;qrnIrHvr@o$!IQ<}GiyhwarSmpxJchS{O_JfIB$og8LH=lXm+)K>+<}>e`%e=2k +z$-HlDowV5Nv9|iHz2E_ipXip2ev2(^Ll@f6g%u?a6#SI?AvL~iKe@HVbINXG>^z(w +z=9npKGc3BtyO}>?4&k|JA!yCv{s!hQPH>2>hKT7bU&Gu*2hTR~ZW4BEL&=JfQyhg_ +zR-_0yJi+?DO!fvAA``jf5`{*(H}%I+#xUI-s9#oa0^YsPnYoUD761K6%A{KJ0ocu~ +zXQ_L(mbL$zoKNESR$wbcW_;LJi+*f{%$e=FT=q?VYHLoMk4_OgkkEfcnN%&kOmLS? +z;H3Y~H974jE2qVdh+Ppm7T$sVS$iQge9CFcTXFl_$TK=CHcYPhHce#<<*MTRTswvb +zD@s<4%v`hzI%h_jiO)`cjt3duMeLP5feb%czoOtIyvSU(qCotF=uQpujlw6H6Or|O +z^8JI8uhVyUFMY3uuU6mTz046?F+Q}UK*L6D=6=4ZGUXTKR3eLSK%c}4gTgm*0w;de +zDi7a2f=9xO-&0-%A4@nDo8bZZVf!L!y6 +zqm|vzun}5iA48kU*uR6b_$T?k&Y6lI^Zlb3R+7L@#I7XP`v`X8ar&%p<;XgqLa>vo +z`jdhnNQvJmoIx6-?zc>lQv@uhJ-UuOr~w +zQyAC%Eh->ugJd4m!c@Rcj+2{US4NeVZQ&=!{u}(Hh&k4a@slLp6P(}!u$nvZ;ew;c +z{1kS1r^Kz+o`M8^vMr_SH0zXxn8P~sLR!}ea2wZq%#S=oyv|(6Y35Y~KlwGdieM>% +zFAXx@Q(2D@0e3jOZDCjF39VxI1+bLwsGbvGE}`T7>0)b +z+jz>cCy1A^RS7)hYGRt`?c^zssmdrgik)*)98d9rrwBbtOXu4o*%$DXjo>M}z*AEJ8N?ic*-#NLkDZ7wi35&B{q5X%S{LN&$`+FOZIV;f~6SbUSOir +zZ?NJO&0WXuTy%H>SV|{(m&x%u`PReWCiie&$N4yVepkvk|6;I%<;0V(lW!F-bo+bP +z_IHGF0m$iD8iZ%qZ +z39SvqXyBej19tSdkz(!NdJlTH>1qBOX#4x{<6+{IR%mKaY5p6*QVR8P-VW?RqOM-> +zA?XkMSSnkNEBg?{3a-L+b_DWT`oVaTzeX7#NMT$IJ6L;;T +zPjc;iP*s*{%^yzolq)uHcYtm9I3`NGX^8v;f3}s)P_1%(rAD}~jNi62lK8vAf_-)C}G-5J* +zTO31~6d49HNMI<1#NxGFE6NInvJbl*L^lLOsS@0T>v6oNB92!cCXANs%GeCxOET1w#@1ZtJkW^(_qSb +z@vG9t2b^Ur!`!Rhvv94*ZOKW#j|Gp`gPDl!YBEy1smQqCgr~upPg5pBE|`1Gy%lU!auCTgM3__-4tKIgt-;da4DVf6;>*8d_6Qal +zT04ZS^Zx%6)0=9}e&pIfuj-&_lcl?k3%Vm&FWZbayHaX&|=NcI|EWJ&R_?C;_ +zChreiq_;P+uclC+?yaS~)Gz#&Hkkix&yJZw|!2ZG;MqrVB= +zt>>QoUGfcW+yxvop(in0K%Xws-?8tA{{92~+yT#a>2^zho5-C~;oXDuZ3o9e+1Fge +zHGSK`A=e80XRoqb`a5>b*iJ2(zJ02{i{Qcg=6i2`p})tF_t#k? +zU8tvc4Qx^#n2es?d!JKJDPygDnaNzAthc|D{TE6bZ^`EHV(fu7vHuO$P>O7dT=s&M +ziCiv-%Vih+_<{3NA0zT7vL|wR3Yw7Zx#OnW4)$q%!(6jfe3XWnAwt)?YiTUpW8|`%AB!iuZomv2PBG(JRPG-5- +zQ!oADIu{znj_dGGu65{jeLcm^KAWDrRJF!EPOT|SWgipvINgD*X8-Z4$N>B1tu^2h +z(_;I;P?CZ{e5PP1uf{QyyO1N$dB+oHosuFi{W!ZW~}jt-!lHW@H8O<(tq)H$~vna +zzD+u%8r$8BUYX!{_l&{u-Y{?R4;#tgc#|Ua;0NLxODsQ?;q~<0zt-c7+2{A+_bKH2 +zXFRiF2Kpd#^fDfQ?t1mH{E?V*EOt@_$d*K-GwaWIzEk`#+DcR8^Hd)hD|8c9hUu- +z{kvj=XJ4->z1ZKK_po-Fn90SwpBMYP^PSkWf4|d7&j;8Ck0dg +z|MHU?u)*RZgVGOVyoi2@j|@sbxbD44{AAkxXD;%SrJ6xMrh9X5{31UI{qzyK8?-Y~ +z>}F-& +z84T=ZjcpRLza5!QLgvl*9s!d*0w(e=vTkBqoEO;^g>8}8kbQ(}^5V8d}Q}=S(`y=BZI&tSFd>d^n2iw}ontRD-I?xl#Hi2)w%`@?32_JSh +zcI&IuA-SW(*!Jzw%Ga75=^x +z8z03OTH`~1{()zu;H4h={A|1*Ua;GD!ET$tuU+JYC!rU>9gJ~YydN3fW$((~%M5oz +zy%+4(;r`_^Z-MNAlz6-O9qEVT0(R@84f#%Ji24(H@lPL#j!xP?kv@9CwpUXo0H!D3 +zS6k2dhny33ll24vkVH#l?-iPv7@U6H?Qq20nHxc?v2`z-Gt;C(CK;oh6pb8I@l +zWlrrj9*2knwZW6z6$tOsR0#OI8U`y9rj4*H70R$t}0wChBUtaf?! +zGVNX(Z~mpVhFAJ0`jA)h(8$ixhj<@9jqFN1UTTeBZ2wRpdf)Y^>{&{2U&~s(qs+ks +zqX01RKe(6UQ&ShPqium_A@%1T?e!=tC+e~gZd{ihgz +zkwe+DdL!Q^lpNc5Dq6V`ny@=r*85=lA5u=f`F@Rxt@n%VQ&rYlzZib41zkC2Pa0VD +z*tLGHUy!3)F=v{ESzlQ12c9f`4&B*F4lhSQ){`}3+eDvc5{JCYcZ_aLVeQ`CkNwDr +zFTW2Q`?l7Jfnl(l&?+_RZ2fe--`AYJ@>7qebcMjeod)lCWA%qv@7JoO_8$V@_OZUN +ziI_q5JSNurZI?rQPw3r2@Jnkm;!o)D^R50so +zx0m%kCx5{Dz#o@%If%y&@$HQq)*>Hg&5x`Ll<)t^et=zINVD-Z+x5x)tQ&Oi66{E> +z$@T>MI~UpwUm1D5=~rX_|QodpZ- +z1v{#5xqi(<;JIimp$1MM9?!OOibPq=pd3Xu7pim`wuSNF04~{AK +zdf9Kd8~nEq{C9U8J92UFA^632RK0iddmDScUMR6J#ly@&_=uZX=P)Ys2t$-*Zp*?H +z4625g^&bP-;%0Vmv#7FL +zIa?07TMPNskfDQn+b`s73A&!eUe}PbjqR88NpNqW`@M*a`y}3#zGu57Z&QyCT79|} +zxh3{#2(fP9E$G+5A3b1Qd*H`G&2HhFVR%#szA1dHp)XtM3v+gnK4R?}`m&Y2$aM$T +z>_@TYCC2V?WN!+6A%_RIlerIBD{SE)7QQKSBuQ4@)(0IfvSb}w +zWIZE3xPxcbzOdg#Zwak}N0w|l+!9yHnjdtyT%B*4w~2CMR}=EEh&6z((T0q9BBzpZ +zep=ch2HHUkByDvh+9E!2^Xxh5lXz%BN&UzVOY12UTNiQ+8}}>XvV+f^mNd?Puk$MOS_mnqb}S7q&&eb3JkYslQar317l>m_RF +z|5o}h`Vm6@t!MPVfM=JEBY$PZk=t~=UvypiE#D)rp>JF1o796Jh}A>iX7Q{g-nSZJ +z&K<;@h4jA}-^JhDaf4T6RjF9tqMUa_e;EC#udgBI)ZAN_$M$OEu+}j=D1D3Vft8qZ +zg49i){>a&djdF07ah6yK89O!19DibdpFg2AAbTTD#IhlEm!+gi2n3(EHj#{6YmCyOrv=o0)Nz(y|p^ma=q-&31&Q_mF3Dzxv+TI(`rH^PzdNcdaN; +zgzdWnn4i3p%55d17VwZI6 +z0N-Wv=WDE!RSxfQ;>E6Hm1 +z&Btc*CHVt#&3CUWUC?2(>P+*>dW{`cozGmT(@&WYF$);rnnR4y5HXYDcSud?SM&9X +zsGbtp#u&Bh<0GxifrOExKK@2Y60|3C$lnT+Jm@5AbBg$z3wdDj*uJ0(T;9bz4&MWG +zyO@_LGBkf)d>$qb9CB<7*{qxM&asCy#P`rxpA-!9J(=xO)h%cF9U_hsImw;HI)B+? +zuO?aFb=i8wr}5>YZ03WJ#f`eMV$61#tA-aMdm?j9Tt9n4Y)rZpXRaKMeWSh)%va}I +z%@TWRe5YB(ztc=4^0Cf$n&mqiMcCh#l+xLu`|;;WceJOZ%@+C9(&w{R4qL)K_F(qp +z@C`WG-!A1vw$CE_Lbr>xY_V7pT#ontym&I(iYJ>~<|(G52C4(TEu-+6ares_A<>r| +z#2^R3;z#gNzx|efn&?gm_hg**Fy9tpoFsQWL~btfUp+rIW+HnsZr{Rg()TRG(92|= +zcoFuymGxuaEv+RNo*AhDE7bGByvE$?BaYYSfA*eB?mh6?dy><`zbx@>$R>4GTXlvR +zGnw;@+5f3A-=?N|Yth3&WYeI0j-h)ufTe$zeWX&hb$RT&hX={KW4XTa)=^$$UgTde +z);cwF|JXGr>AXj{=$-DJd%MS$;b)2H@o2W-(IS2Cc +z2YmSv_U9j@?BTiX(+)C+%X;W(+m*)O>>3}*BkmPFC{)Q_JL?e-g2{)mYsV<_T<$Et +zV0$I~SNTORiiqP{i1A$a)PnKb+=lCp9+7pC^Ew;+cO~_-61U0kR^qliv%&hEXWrN& +zdLcSt>BG_JoIYZ<6O834Eu{`@#|%xT!9 +z6l~oQ?4rnHFgeZtYI2HS)|ZOUJd2E<`f*B^*hCNImRR;H)q9L_3MT8=#0}GG$rYr& +zAhvKXHnE!XjeBza8E`eSTw92)7SKdS0C>3&G_YPCvt5$vMFVy#1 +z+#Z^=c@{pO`Z4R~TX0c|nFQ?HL&S`Ato2@Rst#XEO`6FX+uF4$k +z1P>Csb&7tRWA0b%KpuMl#6}%MC&R16Cug{ajd8(euvvn`9FjFMjGx#eDK9qZDEBUH +zms0;CyVOK|VwZ-Xk$p<;bI>8*TbFss%;veT@rH~0)NpMKSdlg+@I3rN;P+$WBrfV&StyX +znU}@it;l4ZA-Q}p-xFxP?MC(ysGSlMNi0+azM~I)Hir3RkZbrd+`kaoOlV`>JpJkX +z*J$N`7pp49IoztbeJ +z?_-+1U)`><#je&+PZ--0;#*fAf!U>_$G_Io%VeLP*wpJwSBz|C&39De+rHS;^n8^y +z_8ziQnLhus_cXcpz-RA~%T=bg#cZmx^s$lWOCB3xEl@-Qvp*YOL))%Rwrpy_Yna1eW#%DwBCR~KxokLd^BbgC44$lNM)av$B1HPa`xiTh77S9_AV+LO%H +zhO~6YhujyQ4#s)96`smIDv6i>Qokl~y7;vvo5Zi#f&rD~7n`)6ob8~ltVMJbA#xekos3Ky+8f?vUTF5gk@- +z>|;fb#|zCD=^=j$FethxdKe;J7G3NZivA?rx~NOWxR$@;lBTqN@|Us@e!+#kF-x5N +z@-0lkPs7v)W)8*}(ctL3PA4Y{o+ckSXXPc?);{c6=)>2HDgI&X%UGRrCYd{j(Pg1uzANeDJ0y|| +zTuSE(pHN+ePw=;+veanRHz}l5H!heY?m!l(N$5He!F_t$oh9XOH$lt5|Bm+$O2I3mlIGjcIa`IKf +z+u&W=olg%GY1a338pg@mDw}WM^&`<2t&)=%3fmOaLIITf*THQ(lJ +zzmD|^%&{oGD^WvkH5iS)kADd$C$V3A4}UG+@=W2In@K(`P(5CK+yZ?vREd25gjYkj?ZgNRt9wnYW1 +z`G&ZOeU!hYCjE5sT?Wao`Y7wc +z+_NUP`SzMw&9{#&_ZZ*x?L%JfH+}P#`S+Do@IXi**_+prCZN7sZEXx?>(P*X8-&% +zz9}KRc}Lkk{|q}Z_Srk?o~tj^xi;~P90WQ)^Gx>dSo}V}@N>^r@Ju_;zB%N-h8XW8 +z`M&5t@0sXNMEHaS<9=V!&<4P)hm3LpxRE;}sP>nwdMPIzanB=cA`2IBXn*2>C`MYQz@LmdhlD)S3_@1-B@d@qwdUzNGE5svOEZ_(Af4&H0dR^4Ok`YB~s97a~`tgGbj>d5#F +zF_)9848N4GqWx5Ivg+CXf!@qlPxt=o?@xC;d*rldUGI0y^QH3reZMPT1)TWH#QSF| +ze%kea+Pn7nsH!`E?!5Azgd{*9kqi*z=@1?YDwfGi0zn`|Km}iu$;>1fm`8SI0s#?Y +zq&})`gLwmuL8WW0-DS7<=&oI(;x65Gw^Tr}t88d#rE0sVZG~3Iet+klGnq^h=|{VN +z?frc|-}#;UJHPY0=lss^o^$TpnR_l3V*bfxAN>gJosM^!(f@Pse){aCxS#5SYys+Z +zsgRFFvp!k(y4oiNrN@>Q;D6T`7f&1dZhJbbdF^D(LFLK&!_*h&@%xnb7Op-vW5?5b +zlP^{so5qq?8jWRX2X+|NJ&kv^8x`Y9ywiLozW<*!u!My%X0N}H&n|SQ;=R3yCwm_I +z*{`rK(rG+bqJI7Z)+c||o1XdX48OK!_3PNxw_tCr6FOQOV=utzG*6Fz#)7(@R9M>- +z^zG!$4KGek?z@fVfYb2H2JGKu!#yAQlw#~cKL0Py1?r0<@GbGzd0dHooss`)!toDr +zEcYX$-#|Q`hkZ!lBL@GgdmX*RuLs)iNUb;R$1=Eg3+)w!@2xxaa}i@$zdVex`ky|s +zmflH&@7C6zM-2C)9iEBoXY|fhdN!kx# +zrKFPcGEQXv3|v!s^fT@GM&Bj(VJ$t&+_E<9`Q|s?$Nmp9FFn4Nbd6Y(yv))*IB@il +zJ=2--kprLEj~y_uL(^!lGUQLe?~k#6&+aRaIu{q=Kd<-=aT?w|i1)dh((J%g{Kgc& +zkHIm$2i=f{-osY +zXBU^I`M6MjyyH} +zy}}=wZ!b{Oj~2dUzRjYhHy8f1d1H~9zOnG1%o}E?>6wK;FyA^yO(z$=XuhSm?}mX% +zFPKkFdfxmL*2BL*8%$VxJdXZQz<0Ggv3thU53LrdaL;|C1iS0u&}$+1pYKNgRaW +zkZ)-+6*%b|@Yx4sTnm +z9&cQt@(s4W8z_$SB8Dh_D5fZe8Va7QzSfS6i25K +zM-2P+ytnIx3j40-@jkf&NI!@4A*7$G_~ouAk^X*#0sZbqycyB=CiHz1`reMb1G~O$ +zHc$*8-+{a*kS;|&-c4u5|AhINLNQXc>nZd7hzp7jiWQ1GO1oPSdkOKP;D1kljd+oL +z`eoyzS&UhqtotGM)?>41&Q|9e-80YQcSLW#zx+ZUo~2yMVHa#Vw(wP?U%|~j1MvNB +z3#+_(x0}u0?PNPX=JO}!L^o^Nb16ri=cg{F@1t^``DEQ|Pt$*Ln6B_mJTPga=Y3eC +z9eNEm+>2@7Of`?@9*WgZ);)_o(bRXM;amrq&A^}CeTw=id>z3q)Sja6md-DtdY{LB +zc4HyO`^(;-`G10?>5VCP-@KyFeFXO~v+AKszYPjt{(er!(q6_px}L=~duXSvmGY%- +zjnMlL^FuGLJ33}~E>MW~x;4>viO{_XGMe+{nu^vmpR7C1=ftP%*z*SKQhI0XF04`9 +zl|_#vVUbW+hkHiYJB>BB-G=wrGW`dAIJOyV-zl`Qj}(j(uORON{PJA3VdZ_e_k0ev +z=)ZZB>vtTZdMbG4LeFO=;QT*=-lfb*=TE!w-J}EmpT_;c_c7m|#=YJX=r@Z&!MwWT +zV?8UJUzOD~aVgt|^dylEA$^5N`#)B&?~MgIxc8lJ+rfTtDan4Gk3CcMCoW>2)Qfty +z`y%#2eHq6jM}IKCuiE3S@w=j4i_7m1xhdfe1?t1zNF)?yY?Et?Yp&lFtev~2A-bk! +zwKot7Zz~IjDQ~p$9&fw_RnHexPvUV!T^3)&Qtk5a3~s+6>hsUt?yH|$iri(PaKIHU +zYjk_-qrMO(1WpY>Y18YmM10%5WW*PAx!nz6)R{E|HwQyogO<4VguT&*a1gEWc!d$L +ztTEXvIVr`IYD_bv>oas_C37${mcm^HCIZYu0KTbXcw)lvOBnnRmtoP2cU%Ff0EP*c +z4rBnBUKrWR0>%K@0MSre3T!E`qk|0{%ITr2XA^K1jIL}Ra!UXg&UPR03N#gbm3W_$ +zj2EQ>`V^)Z0299VN(S&(BP9#SOJ|A&D9&Jt4XBbp1)5L?KnLgn17HM9KoXD)qyVWv +z8jucT0A?T)$O6Uyc=W(>fLvfKFb)_Goz#9dJFc1h@hC3a}Kg0VRMP +zZ~&!18L$jk4wM5cfE$68z$#!hPyws~DuJ7TwZJ-HJ#aH{3ves20oVxK2HXzZ0o)1P +z1vr5!zy(wTZomV0ff}F|r~`b!CSWt*2LeD42m$rL-vD7C0z`oZU<-2JlUw1=tNd2DAcgKs(R@ +zbOK#KH?RlT3+w~F1@r)q1HHh0;0fSK;3?n$@NM94fv17*0M7v51)c@I2mBrI_rO8m +zAAo-Zz7HG%o&%l-UI1PMegOOv@Xx?Yzz>0c0bU0BfLDN5f!Bb;!0W)j0{;da0e%Gh +zJMd%RC%}II{|WpQI12m>cmp^F90yJSZvt-tCxN$tp9B8|yaT)ooC5wE=m$;%zW{y- +zoB@6X{2F)s~eYway-%1f?WFlU*8z +zfE$Pa_XAG?uK~XVC^F)S4t0PYkP%8fPLWCHrE^XICIj<;62JxA2fP9(3etcABMOY* +zeiySNfbdq*fh-^oumHt?4X9#qMA5)Qg@T2`MWqx$sR@*pKU5X6Jzr-u{iPbV=+I!;7igKUy{^bPEJlrGI64DWK^Po<}ze5 +zjk8=iyLiDh*Is{vt;A8btbE0dD_5;vQ(1Y_+I8!1zWJ70H*DCr@wVGgs%;O`A4t-n`il3V?#aU&Dyf +zxC;$-duwXze4Eglfgt)2y@!6I4x^r;2Hkx(+H%i5XvV$wq6Mfv>P{6$tx;vFF4dH3 +zNj2oPqnhzb@e1*}@LIHR+Rf=PPOU1nacWnoLr|xnE +z1(?AmVrI$1Y&MRKWx1HyvN79aVWu-<=1IqzC>66%GS*5atZEFHp|F1lvn~C~4NI=S +z?%KtRuDN>Qg8B1`=UT6tGkey|8Pl(vRy1|WWJ}?sD+=-_PRJWSZftH&_L!_pb4Gev +zYD#jF$!O5)l)3fckb7>#>#v#XYG~ZTlzjd5`_R7mXkanEs?4B7>5`!&jemx5C0&f9d41V~%fq^dYqu>X@d(WaC;CV0z(6ke +zLj3UF173+QuNuJ*gFgk{5B?&!;T-%;!3(0eB~rHI=TnW7l>Bu3TLZ^fQm|+({6d)^ +zlFb2HAX`ELP!V7QUV^L=oRwTvVzzwC(4)7v>MBmXd&~?8#8kEpI0HRYmlN6A0rbbp +znk}t{Hho!-t~~Ylu~TN|NV9XU>eH(3SxVN4^Ou;jT6H~2S?Vb|O&Xo!Cx8X{@*E`V +z2MQpQt3u+x2WCLF7@P?^rEzw05tHR{b}Xb1St(=(sf>tTV4`d};T%J!2g`*}5&%%Kv)j?VrDO^2D3e0-ZBaLO$z- +z&3$;_UYW*i_9?A-ZQ~uCxm`Jq?(98dmbZ`V80*-ZwJ)>0$E=^N7|S!(n97$YTGH)6 +z#!gdt2<%Q%c_1-g?W1&DLrq%|8#oma`OgAbJ!I)z0$+kJgscuS>O<+j71Rx&T!6li +zZ;Z1;aGC; +z5y{R#_B3Q|T%yX(LpGxkI0qSC_(kpNOR=|iC>`xeX=|6#)~S?swkch$ +zilgT-MYrq}b=$Gyyv?Oh$Foe9j2~1i1gElUKT+>2>&bR><#guio;mrJ1AWeWTPRRI +z^<_`?O()-CHWZ~Vt3ShdbCeujVm_#}j%yq1=*;dKQ{JB2k<*>ECv(|e^S+Fpbp6Y^ +z;oZn>9EXjE9>&;!u>>|+l-BIFF^=}Ej!Z{)`ku7Xy{Y?B$~(q#}1r|6dsv17vDMoZEx +zC|U(O&+b8gkWcOACf(Ll$|~ib5G?_AL1*7yT-S6nG{kpn$T<|}6yXT)Q~Jc(+EC;$ +z%6C1Bv3DFVe^hCm*fzn@p4Tznu{V3)n9}atJvrr_wu3MY6|mG&r(cHOcQrSYs!rj8Kpg@byNfDoaG&9?dguLl+IMgo}}*Nsy9!RoP4X~=Wol? +z@dEAX9ci7ZT`Aqkdy;xgC8=+ouydu7v~sCW*B;G#OYi~|sy-3PqL9sjEPkAR2(lu` +zC?1IJ0QNyv3E4umo|UbsZ7KF1{f)Ao3Q$@F7Sw~WP@_JO@ZK^`2smn<9ZBOn< +zqI9=`Do2vHOVUb2ijPyD(K)dpy&T)$!OLDZEI*g7^Lzmv( +zqmI;a+*aqxa+EoQGJM>@oYJFnz?W)!J;-|zc_+yph{m->$aZ1=r?HMLGuw>i$;K5) +z4&#j`BYros%3xfrH&*C$52cz`n95BRrd6iZrW;Kwse5iRGl*8uAs1JWPmjRf5&W=_ +z)=xw;fOjCHwMCQ!CFbH*Q=8GzZs?#%wvwDkLyWyiGA&Z4fVj-JWOyQge?n<7p)!`#PpNQE@Ir}RvQ9a>A&!Pa@$szRC~ +zw#RS__YWOvq$YTG!JMV&^(}dK=Xj+0nG!N +z2Q&|89?(3Xc|h}k<^jzEng=uwXdci!pm{*^faU?s1DXdk4`?3HJfL|%^MK|7%>$YT +zG!JMV&^(}dK=Xj+0nG!N2Q&|89?(3Xc|h}k<^jzEng=uwXdci!pm{*^faU?s1DXdk +z4`?3HJfL|%^MK|7%>$YTG!JMV&^++}*8|vmic_C>U-r3+^fFAmFPnej-Lc|*oAMo^ +z{Qa@WqgTKZ?b@w!+T(+$UzDf)MTqcPOimL8KP&PJ#Iel(!=-@`iOz`=;}x!)=zZlx +zc$F!qVo@JaU3RlL7R})A;v_|SO)=5g0o9;TV-{gwzCRhS5amRBq7c!0LW!hYCS*O6 +zMR|ISE)l)^ljzcbTK|4g(E)KR)EQn$&fE8QA|Lz9@nhLe)yiM*^StnLqQxCddLl7r +zdn$90w&%PVf4X_+_QT5O$XnO{7Ae(}P23oJ9%Vhg!)S5#zN +zRXh_#sDJ1^u@tz=ONx{QF6<>Bf}~XNk#uA{FBa__Nk_(bi-`4+bSlL;@A%XG7%~tak)I>ao*= +zWq-V;n$}|vioPdCl4r%n$M{^@Lr{*3L*>~_j>kjgIc%isTaULszbMALq55NE+D$64 +ze^vSL{-PR=e%=%&uN`g{AJUGzUZkA%#v0vD8k5(%)Srz6)jv^A`$LetNlsXzfQ|Fv +z{p%3=ay}O7Y$Fm%T))W6)y5Tu$B&om50^g*In_tb`@A?lY&s#$;_bmsfZ)_${X)+7-heC%0YGvL9h~_78A#dqQ3h*y +zj3;6rv!PFW1WGyW14mRLe2nu~0=flUPZhqcDx#}UfqZh!`gII{6{20QfjHO7bnQC +z5b{bPm))~S$m@k%xSQQ1PiJB8e`N0m3KjCD$VA&0X#eOJo& +zsmGN<|9K%lC*&p^6WRD@ChJXz!yhC6^RA175AuSs4{2w^cVZ%*c=s}P&OJ0#U)njE +zVCT1&vD5$XP&=POpW;W#Pm1g$j0Ys|`X&*E +zS4)JQZwvXs0#(58p^$uD$oqs`&I?DmeE7QSO`%^T=Eo{w|5G7v5q5CMDHk`sRG)KS +zRgde%by^HLt#hQ`q(1FEqhN=(#P#q-VQ0OtFQ?C~Lf$9hHdmatL&z%=>hpq-7mEQ# +z>c1uANAFV&SVV2bZU@gQ7k- +zJ2-i0jJv^3VI`~$itjY +zPsmy84^_MTPERxxjySP1BXfrW^?q;E>#-J(tl_Nj1$|CeIPBWy^ai8hZLB8j3V5BK +zhCpB&io_i`p&1>bx(P)aqCUUX%_zCu@2hrtyfuDT)Eh7I1*J%2LWwFC_C_Mmt8Vc5 +zJzV7RI-{;?zZZ?#FzFY;#5$ +zs-w_j=pt{>@@<(Eg3%E9WoplX0H33)9NsWl*)c6}B +zb*h1wOrFqL<3e0S27MH&kNN_>?Ovge&~i6W=Qsntpfli#y6eb&!|nK8+qaD@oj+V$ +zi-vhdP!D=tVJEMF%O5KxyC$*MVhY~IXxQbHYSljUoAj%ig_5;Vcwl&r=-(kZLObFQ +z4SFR`=B#!_27N0Gkn*S*j9f>xN1Ta}bczsiha#x0xCmsQAcnXk?%3t=d&3LnBS)m1 +zwcemN>>E4|dPBijCLQ3~j$+GI5A*mU^}OFi(U_JG*Ew_7H#lp&u4n^-8|EW!L=WxH +zYITQ#5i(GZT4OLl4AM~IbS_(4vbxM!R#ED7#)rD}xU96ZI@A#K#A{I0#~-`=zS^MI +zgP@{|gz-@&K3@zS7_j217_Y!DqrqVz6pqhKtWSKNE1X~y17RfOtaAn7=6FSEWbh>M +zxkEZ>czcHzc6uA*%hSjb@CHKRZBFh27lz>Ye0A`U4o%2Y10r5MA&TO&q^o%sI7>HH +zl&oIih*wA`n@~};B7;sCT9sNfLsk-lDI$G~*N;HKBM4_%xrmqY(zOt+U+oZ)<6ORK +zjlE=*bIr14>&n(U*O%B=l?{ofAsMoLkyAE&MicgWKFK+q?nW0~Dd^GdLn@-WCBAB* +zdT%iP7_~>l#Y?`VTX-~mNgH&Lf6?xdPL$n)g-v9LPn^!$00J@+bz#1AVwK_@lKlm% +zE>FvwANIOD@h;_^H%ixe!{K1ai2&q_j`&i{irE~?81QIV<#jR@3}Izd8*H%hHP2N( +z593F5u1FoTdbR~oQsvRGnzO|lj!+zNHF4lXUfAn*Q2~*v$HK_U-EWOznq*d5)gdqB +zaYbFs>a7#&xH=EY$|JRex}Z}_$TXFrQ@{YPjw^u4$y$p!ls>HdY^vpITuGQ+0s*WY +z)k^Y~SzXa+*jL?v)kysE`U|J&IXZ4Ra4)5jd`>5Mk&sI!@2BLk6^TFMS+guJpXW(# +z6N>mchm&02i$mo7^l-xpvK~#S_x2!+Oh&%x0$?~!1lGG`n +zWKm&xepz0&KL{NvZzpAte10jpC9eEf%I8{xzbk0OF{$9TnUlPqmR#QVifn!^nI=wA +z7++pK50$*1l;TL5ZPnMV8Q=J#( +z`-K0b-(PtV!@*g2V=TB^>EHB3|sW03qtTXl}WQd`L +zt2BxFKP@yYvVbs=GSC^USvZ*&caa;%l2&+j&9pQI)Nh{|PC? +ZQ9^b2<4NheI5jq>K-J7jD3EZ-{uco)TXz5e + +literal 0 +HcmV?d00001 + +diff --git a/native/src/main/resources/libdeflate_x86_64.so b/native/src/main/resources/libdeflate_x86_64.so +new file mode 100644 +index 0000000000000000000000000000000000000000..cda376fa2c627f3457d3fb5ef79b2791f47118d7 +GIT binary patch +literal 61176 +zcmeFadwdnux$wUy*+8Pvi4rxnD8X(|iMJ9DShj%8;NCFX?f@z%tyr~c6zhdT0@zYb +zoSlT#aWwVXi>LLRdfxihp4XnEKSWW;=8^#30#rb)8mLN!2m)3JDCGS&|M^V +z)nlS;$_F(!$bCyoP~phyAHx1}*W7oP@}0o>pZ&|B{j#vXFT=~Ou)o|jeAMGBp|gDc +zpEzk+XJ>EW^-=$X98WyT8yQ#GQOXOMhojXO +zxQOte+e7_^^ClSUx3eCr7VOhV8V6?c~aD2+Qlj@?1NSYp%KJwmWXWW=_p@ +zvum!oCUQ+`YSlG2+&KHjn`X|bxpDT?s*7*EY2A+ +zck9d_-59C4dG;OilJl>>aaPUDJ8q9$a^2kPuDSk>+pd{=AJS-+1Hyw2fJ_ +z@3Kdp#@nu+b!UXjoEvK*w_SHDb0kl<-7)vZ2-9%= +zd2{YK?}Et0bZY#?*PMS|#mL^<^UoUvRlzX&|9t)p?hT|w-+#R3&*0rqUgzZ1LqB^2 +zF_IrC56fy#omnXDjFcw$%gOZ|{f-FfwltL6x$<3MgQGk2XOUca?z!@a@>%Bx^`;+D +zzU%)4j>_oTx4_ZZ+J&=C*@i%hc8C-E0=eF`}-RP{)U0SVc>5V_!|cP +zhJpY8W59MkDzM!F@4Oy~*bOZ;c|Gm6v$9}C@4$LSZRJ(ZneX$jIkb{HdA?cR`yD;} +zo%xnLi}~TV>*w|IEGCfOqMz5wvzQwGANBJ+JXbgKtp99p+?y2G-T?W|j(69b1UM7q +z9&ieKI?SPUQgKCirT+ic^+)}aT*?pG*+coZ+GS@uzhxV(_PRqgB{XnS*g!!~N3}UL +z(f57bxS70))P7@{?Tp(iPQ9%@s424Dac5E9c{Lz*-poI(11z46t8@M4uKtgCS$&>4 +zG$T+&ny1<7jmvHIS%AkuJyS*q++VTX!M1wOR;{*s$t=OBt+rovbSMir?01~tzJv3HU5QNPer;IL`|&~Vz2AE$eI@9B_doRO +z|I>rH{@cIUe*sP9zSr&d!cZK*kgQnK={ypNINe6gd20D$p`M2IHK#cT^5&ldR*}B4- +zW=)-zP^*$^wT7_%qn`sq+PfiEoOb^ZYnKQ2q1a}w-}cg(Xj+AI)J6%wIW18;o~_-j +z)(h=)iNjgmO85~5t|~t@M@~%>^IEJ_-G_zdDfwnmySiNyqZiw+M61iUeVH= +zaO1Xni*gSL2brzd_&r+-R`Di#dK-JUu?2(Ny$UYI(?y=AxpD+{C?85?-ZIZK(xGaMNZa +z6h(IYKy3#!Iwj>zO-2%9rp5})CWltcdoui+aBq#3ES5%-4Li(+m*vIKhy}}lzofe$ +z^LetNr{-hp#QG*+~AK1?F +zJoC9r4pBMj4!PGdK1-+}jurp3w#8B(3E9M3=fpIBCe&72y=ytGM$&lS&Th?v7zTIl +zJF75i2LS0u~ZOyQ*w!UXwv!ajxEN_C5Qmt+}Q*6asOy_edHms=u$W@C) +zTvn-WOTDiAD)pDNTE3xUUpxF?B-25~h;Cl_%cSOpjsR5wA0(LKqNLiDP#;?5w3bZ{ +z^EZ5G#rK;HcY@H|2$Q@}+pX-Df~2uJnICnPZ*36rC`cIVgx@T+#@IbUMcw%7d7mWI +zZra=Mq1p{4CE~uB8A8X23V*50g89-KXZO(Z*TPEPBvAUQRrsmpd~BEvKcJ?}uQ$m^ +zxC`zrPN~=Q{Q8&iq7NTxJLrUbVnxOdK_|pnpi%nU{XIPb*I_dbbrm(NF*A!0hiP@d +z?}69*$T^_oBT3jFtp$Q#%{NKm0bz2>OZ8}E3Nci)B;xDLh9ni71+fxsSg!f-q;@{)mCf6Rx5sB-uui-LcLz-3l3B^yQTP8xnb%PMtj|> +zEvje3Yd>^EJT!C15#VlZqDqg3kq*I^c>}S6<>pjkH&C +za!S2!U6E3UtZ7y14Qpyj{dL;q=%t%Q;S9IWJ)2>q>bvD)n-j9ObKonss)^Y)(2bBf +zr73UxHeR)+DVb97cEt`G)R##oT4$wIXS%$@cDe_djbA^O#@uL3y=iagunRk-Lb2NH +zygWMFnNQ)qcBf-d%2;W;Gh)T|x-RE0`Oar~X66y(V8h#H<~1#@ +zh8+oS>^Jl`wXk>HXQ!Kuf8d6;Q*GLqN~uq!iH_8U9kf!uBDJrbW^C1-9@d&JZ>MKt +zA6mz}f0`cDIeJh4wlO`d6I&@|Y_d0epx%Uz8)Za(e!6f+m3qh7GALF4YL)Sk(=*6y +zs^D9v2d%S9Blm)|7yXv4&bN&N@~>BPVb^he{A#=B+p5BLM_!mVmfLD%JZ)rC@z>{V +z(}vNdMo9Q)UdXJWgr8~_J$Ab@=bQ( +z&Xn=Ft@haJQ+wEQ%Po1tT(nkgD&GLM82aAUO0(ffa0DZ(`2@hyV;01Qo0&K0f*!1e +z-%6p5!j9%WYP&VDGWBNNXWyJV%r~HrX3Jbu5=%KNjbNsoY(d@rZ<?L<~g7xXXVZPq`9_BTnwk>!>K;pR+ntH-ANr>JGesJmvnO7#dR0gZm!*_ +z_@;TU_?tzn1bpcD`C`wNF(=NJq1YC)AGXU^!vei*lYf_Ena|xC%hN`{zBj6^G3Uer +zxhh`{H=o;)F7LMEtIYd12_h6TzFI%pP#*TDw!OGX$I3dV{Z;HY?o!6by=m@ZRnbn^*z`3hTYP8%Ke*$I&ZgKX!;*yBtt +z{8tYKZTB(#m{#3V12K`0^jS(9z4|!Y3Hd_Q(1ks07xN{w3Bl9yh8@C2Z^J#d+6QtC +z7|d1&g+DTX0J^;^%glz?m}r%?1NI6U%lZZOhHdt~P4=*Tw$XiLgLf0&)Ss^AQ)yYZ +z>VH?u#E8Aj2>;AQ$J^%9ujl*@bqpYs>A9474Ck=ZFVTXXp(3fQ)`zRMtfJ0)-UFwJVBkMd@UZ~^yI>#f$8}2+7G7+wd3*8*pGp}ysxlOtZq@m9Qj?)U3HJ8s^HwDQyCTWn`P2>%oPrrfbw +zLv2%U2GY_lJeXFmgG0OgbuE^BXUpA|d+&XE+#7b3znt}po&BcSG)jd1MnU|-q6_F@ +z-mj57qi>0%jlG~r1b#xj)r0!9o;SggwHR&@%y~b`3P04+R9*sydXe_(4jPJe+p3R1Qj3xh4K5iBR$L7Yn1cP9`H#;A@1e^VM +zXB&jMBhTr|yLe&I$qDZ=BfRQoY=`hM>MS21w~XUtujC^CH7&9#BhH6+t97(6$mtrC +zHgA`Cmh&>;uhMXR-an8!r%YJug*X?aZ7-gxJ +zYFily4}_)&6eYdtU)ynMc=g5erw@3%|$80_U55P?#{CoPBxyvOPhBfMbzL2;JCWE +z?z1z^Mn$K+LeO# +zhd++$h_~1NHRU}bYiC-{v_=ZW&$JPQlsA_4mgk|w!us~&g7A+oE2)uEx+3+P5I)RqFF@hII&I5kKE>+Tw;o#(>#Cn|BSzhZ9sbv4 +zz_4~`vYg?cL0f?x0ux6=y6w<(yGrQxt0CPo0OtU7JBbEzbZaXbvN3Us{eKqikMRxx +z1C0L|V9`G;m +z?mZ!(mj(9~2%SUikhze%KLj4q(D@^UX2ndzCJ>sO)k`dzKyuV^I7MB?=lUc0;vzwr +z9%KvBu%acf2On-RTKoyX*nEV)V=WaWW=%_}gGabLrC;juRB97S`!xMs9y{mq6w{)f +z@!@F<^PJ1G1W}Exr1q^Gzh&Q$m<**eKULVJ-bLNp0+2MsF+#*<$DlO&eKY{2RZChG#b8QNbtvpI%S|^#4h#2HMpkb9WZ$at +zmvMXW!xz2_hCMB_q_M$*&oPaeGdjmV +zeIzjV`e6D2xG0={yU_2Qd|Rzf4{Nd9L_ +zt`>Jli@6_&-FGzqrr(q|J3Fn1MTSq%I{Q`7to@Xb*7NUL@wOU*L-&*{ +zv+z}B<9E)&@0=}ur`ed)0<(w!W+kKjuYQl;8qr2ioya+JQH1`>?4>!k37@IeR^x$U +zJP?eh_whlmuLHdB*kJgSR-q+3!#^$ +z%MYN~&HHb{rNfkZ!*U;bSmcV@q&6aPFkJSwB8)x+l`FS)e)?+phLu}i-nVAu)~Fu*1~y)%p`IVqEKB}g=%fDtbzxjACU!n;cv*4c8M$oatd*v&D!$Tl<`;p +zF${*o&{YY`Y_5`T-*I+|yO^KKb{=Q7HUw^G$56}ZFwwpx+SRl?JF21xwVbPo!GpVo +zpCbh)(3iC^i8>pfnR=>a{lK_q5nBi37=tmX&3YsoX`Ou=A2NG8B;_~Pd76r_)Xwd +zSF9)Gn-6CE`v9*WMAglX_@g($)}cQts}YC$qls~mUWYWe$0L;+aY=&$eA3_sr!=_1 +zD-CXBfVzPK?U%|uB`48jKhN~sv|88enf?d}MC+lN3tyF>G3ry!ChtNv+w78)V0`b +zOik#crX87~ooV&)kv^(?SKPUP@4kzV+HV>fzJ<-^J2V5oi@!RbTkWr|%lWJ6<0AbX +zRvr`HC3vjC4K6D;mfD>w2%J_aTbMoxOkU*ws}|o0DCsc2)&H;ft+&xevqTV!yUyru +z9~WsmT=eT2lq-{hex3Jq(XE6xgXp2z^aS2jpnCBj``nXeF~f`f6iw44@M-_62|bTg +z5iNn^wG-c~*!l2VE4Qxxl(oitj}PqVv{&H2ib>nAHSi~tF%rtyrQXFN5-km7d^jjo +zz8l*|Tl&};f!V~g<9^x0e4O4~ZFEtuDEe^F!o2Xi!OTzq-_iXqEr0O88Up`od*1*L +z_rD&FK#~0bu5Lcu4{H!V?C|$r;*ULCNzIhfAJDHjS)pIo-yhQgL;GWRVk636ZY>tt +zqA~fvAArmU$-*!lptt+eRkkQ%ZGx4r5KXf~{FD07KhlQP*SnJa06fCJ((}Jc<{wY& +zPcr{GPwWZW$j!g_I>qYUlk6yZ9&EHdF=5iu8U +ztW=Lhc!|jz8|yro+10*K8mqHRY{ycKSm9pMUB+kNyZnHc1coJpZ| +zDR~MyAEvY~UxW7r%Z8sDiF8Uru#GJU^e4U`B^7(YV`tZ&Wei!&c>pe0;eS;$N^iPF +zGx;)q=wHxt=JLaybvA;x;G2VbPWo3~RP;#urtK{#9c1r&+aC6wttMj0b@%3n5Qjh3 +zcJJ=)IrC2HH?Qc2Yi-}DaWg5m)u``sSxRHt^S-R3{hwr#1b+O5rg^PjTY +zPqhdxZ#C2==k>h3**r6GpnePW3u@0Jb70`yYxUZV`A@35{&h_7y5pp+x^0G;=vL!O +z=phFets5-j4P?DN}`iY$m&PQz!K{<*Y8)yP}Npt|y!8B|E3?DmAX{ +zDZs^lkzTilRl6286T~#uUp~o;K5Dt8@r`p%?@DXFSM5?3e|gS0uLM<45Z^OzELjRr +zwO7=HpX3(CH_rWY!W(a3u~D@t>6TjYm*=(-wWw0r;N#gVEL9rhnN+b(CHL0UgnP}D +zv^RNp!o6wgw97B{&KZ(yc;C!40;l*9ys3D*Z-NtU<#4wkX+FKeGM|1Wp_b#(mak2g +zFSp{WEpz;O4E3n7Ast^``$lr++pB2=PqmQxf381demxSIcUt|y9#+%mowPVorlZ8= +zotV_iQ3l6Z#)|CDB0W_J^XU&0#$OWgx96M-6n*`>#ci}n=F$}L38K=`N9vBL6)kNO +zr%1UhTlJ?AyZ$4Q*%d7*wg34t?$TZ&w$QFWBpLT<)keT}ncT9ld&q9sYi7{zI!}Wj +zGC`&mqcu^qP%>TS;peAg3sdBQ*xrk=h0^HWu0dfwjPV+FbL}1-xpNLpo_8kvZPkhU +z5FOzhFz21*9Ei>>bq*BHJIm=F@B$`Z#GJUC#0ddzh0`5eWn4Nrk{ +z$ecIKITW3HnscadUQs`I0Hn9a{y_E5=eUB%R!Xg|XpwdFbz~V`;+;c2RwQz{@-fuF +zcb$gsFDws%+{(SHqBR5CWEhd8_Z>tBDt^HzP@IvXNFrVeg_;wHVzxxp8YgfDRHCsHMG>OZ;c)UarV{SB4zM{$-S!by~S9!O8Zms*M +z#_okrZB_4L?R8#{J7hT@8MQkCe%x%i$2(nc+s7$S6zaQfu_EwyvQC}o97Sz{TD6;* +zV!?;%%&n*PCr<1c?S?g#K@hfgzs)6_=YYxuDQ%T8L6i-pZ@0Y{>>sKK;Z^V7rq(LR +zS}#60A9mYq_skNilA3MzR8mR)%JOYKtwM6{&no@OuMfnFU7lf|0 +z>bFXHGQMZ_47JB;&6E1MLSxSaH|D0iOAV`jg)oLy|9%fKuVmrY397_R$BC+#nN5J1 +zQtJ}l?PT@b8Y`Nkj?w{Zi;~$z9?-Yt?Z^j{?%&ax^c}B>70oVId+0a-CK2B{_q_To +zx+i0+YD*LnnJci|lk2xiS)%YQD8auDU&JkWCio<%Z5itmYG+X0x8!~PneqksVIWKk +z0WJU9C=*Xh5gw_JEs^dW)>^OczVr01StyEnrv-n#*x8Q!`xrJVcB1up +z4L$Rxx+P0gdoaRm=P~38@vr2?kz_@N)iH^}wW?%EBED+g3#zlLFyLGA&%7x;!1FUE +zfL6%_{HW`RK6%cYii(!L=b*|^)^iO~@G2v!4H@i?T~fbIBYR=dw;02A;=WO5hfxb3 +zSW!X2wX(>6Edzp$rp9izjn}#gHGN?~M)L#7u|u6@$2ZpQkkqkKj2Um-P;wTn#Ylfy +zbO_{;*rVTcr)I9j#*tOSgyP;@w48Mfao5SFI3?RrP}q* +zzpB^IQ7xoSH3&fdh@dcNf12&x7ZGlk1%O>xLnNJiw)A-FEfbSn%&*RckenD-w&#@* +z+OfR{MTDfi2Dx0dR2Ed1mGRHj{`aVPm_bCoQ^={>&~iSS?luWK>ntYkr;^(R^V#+L +zZ~9#{vRM8xC$3H`Ovfg(WY=^&Vw&8Ob$ap7Hue)G{tWECT+lZ^ZFPx%xt_iwb;zay +z$r0Uwm@c!u9}l2Za;3sf(vPE>E} +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 +zm6tflz|vLQyRIglCE2%!RwU`l%Eo+zXTH +z^JGpbv7bWB(D${9BDLGfesluIJJj4hE>cH)`!@nEYGQdUz6|2Enp^0@PW?mvaVXeDbWlLK&Za5cg2Vrym$!;49|FSFv6@ +z4ND_+vatovp{bRv#5i|RrZ(cAD0?A#?IX<}+nDN*KmIKA1%D6#$T7z{0-U}2T5BF@ +z$gkJyaYuoFGw_xM^D|xGm*?rs&vb!bKLG!9fnPrW|8(u^_QEfxRuxAz{AN=GaG^7p +zwPKBbt@Nq!q$P%iV~v2KbhD#{onV}_|4K3j;ixTZx{03ZUgIRtV!G9d8X=b^_8+FQbK@Bo! +zk08aLr$NpLHAO_kA=vyK#7V?|K(v=&s+4F`WhfJ*2}ZX!g_&$lO0LJFv>e#)zorIe +zHEh`9KSvXs2Z%ex92R$-rso;!^b&XMsgvCqr*Yubdn||Dc#>9C8I2!QRWKfiu{nr#`$Gx5X?T@izRrK1Xk4ggu??{i +zkqpIMLj*rb^#I^%(SHU{eon?V-Ch7b(1V8G@eZ>*x*2Xs|@HI +z22@Is#il1|E5JW*V|qM^3vZC|>;(`kaf@A^@B6-ns!ryqPY;E(AE=^ndS(Mv6e^;o +zM}SC{ibs`QA=6%Te4unjqIBSlfE|e1_ft8CMCBm-zo>O~SplVs(iucjyZIa*Rob%) +zs+B#kTFocwqcx7+4&xlkHyfW}-B$RZR2D_ND-Nd1x9dH!pI~x&S9~rW8zFGfNGWfD +zXd*%}h>`&tzQg*$wav0@pjuE!Des&~qL2viqLUueI*I(Te-3rh6O(lOA))?33UFG~ +zD?up4BEh&%Be8Q$6cTAzX68yD2$a$;Fgkh(v16c%X0HE(0uq42wA`U_vW5 +zCH{&XLXU(3hQO~wG{5RC$r1{XAC2*MeJuQ=4wY~8jX-24WZpIX2}6A~puayse`ic~ +zV_(W&mGOq=4gXgR2y8?-mJ-%%xJ;s(gUCTdUki`=S7O5UF}Z)5 +zJoZ{D?9{sg=}V{#VLz=z`x5s8SOB7yX6+xz`#Xuggzt}|Weu}_y$pnfuTRpS|0;aF +zSihbnv+{-Pb@1Ik)>0HJm(xF^{{4&vUlU1pGcGEKUI<124$7W3M7O>UvlUt2VxemKMMVTEs;mu76`0kIZP}`35f+pR|&9=B~x&@QF-BllMwr7%Nw=9im!+5PUj&6 +z_B6p}!|&?;*vW9qu*3D51F}$)_MX<^a`wTo +zxn(o2B@wnxTipX5AkeMXRTfJL8G`M4)y7uO#uiIWb#H97T#Y^93Qw?}GnEw)Sqd5$ +zfwX7nWt{>lXJU_2nIrJNY+;AAXBKjj6+NAAzRYNlGV%dd`gB=>sq+3UE&Fdop;J<$Ts&{?#V0Ks3TGUoI)*Q_ReD67s4)AP%V6_?kTMo_sYb +z>0A%ogX!#oVJ(UCMTx0c?!|^!?PU7FJ{PmXgU6UbFz2OF)fRuk9@EVc0RM7>GMyDTb1@*)En$TgG9<65x;0u)S^N$v(vng +z63MCr7dI1!H)trItCdH_e8VtsufG$hXSok*P9+=6Ig@T#psoC8He&?qC4(ucKOjpb +zPj4yZ6RKVgz>P;rnf6!_P;q9#7hRLUU}JexiQFa3qCZz$J%1XV>MvZ!VHCvW}aI +zudMY2KoW2RnV_YFf1sYwd4eS{RwNGj{NW=e#@egf!62}mW77%Oqx{JNr-wZ{#$&1*mU_fh&doE*8lSWf}cy}aH8yr +z<^KN)t5)(G5kX2C0ml{^oH^EJq2y;v&kmCK<77{g)4W~KmXmRpFqoqMe!&BpJ+ +zUoJU)N}pd`kZ`|Un(&%MT;}osvaonCR(VXMQRSAh=@ttbHJ9P)+t+Wj +z^Vxkx%281sIbUN3B}VVBnNBK2p#RvAJ9_`@Cuuj(ENJRlWX=lm{SIq)xwO0B$rl7U +z&&xmbf3k>!%4$_5s$jS;IdCl25>nWbD9`GQgauGgQng*;7F{?A6FM!wPY~DD<`-6} +z?!(H51?49kSzZ{Ff91$>y*}?%4(mD{}ZB2SL_%0xQ5gnj@x1>sl#;S_iUKyz8UsU2jaF2IWK|a +z(SYeGx=lQUUe4ZL-3(t#eC9-CbfsB-nW#f~zKnSEW!wZ0@bzUKxd-_AGWdEee66EB +zPz1b%S|=rTLOZ*=PLYvK3AwH50pySsU&#QbC(MZ*{yjX~6o +z5Idnh)TDy?#th?$2=A|47{>gerU1do5hI`N5FiDpP@l-xdm-JiPeH(9qhLK$TA)g +z1uyvdRwLx9|aSI^XfU9-{~-9;X`ce&Pn7gi*}n& +zP(@Zop4BdtPJi0hS@DXz%xJ&^AA*T%(cM+jpMa&BSF;;WjFOKnw^4x0HEpRz*~!=W +z*wUbp$N4U3L`W&X40rQ`a}(pZDV!=D1S!Pt>$l4g;fEQDiWQJK*(d7Ch&bvoAb&RS +z@>mwK05tCZRzghT)5ww!W3OP#e>ib1dZK0K{NF*$s@vbg+%aLy7Bk4S$&vEfnFFNx +zGB>$dl~Wvl4rC;MeGggyX{3Y(_LJR~g{Z*Q46!d);U}GaYixdKe>`_FKQHh)o0M5` +zZ?lchSU2Px33x3xC#+1#oUk$#;*OM*&BAcSolj?br-xccOwR*r4h0p3469XHcf;>C%eRbc#ypli^!0D3C%6u*i=CieTKErvGp|;RR +zb6Xz&Vn;3~!K=M?H;0YOIT)O5&X%S&O##9CZQZH1q}=k;6pgvVEo|9Z$;IHF>wC{@Y*lCQ|zfQUO68h$fcF4OT%@&hf94 +z@q0}{ekYzq%9{vaeFLG +zVT>N9rH4z5%t`XmxSJt8R?4UgPTYs|2J`T~8VnX(uIoL;-13*ONv?+R5KjLzWG` +zm`1slv=clkhBXts=`_lBk^xwszUEW79E&zKF3EIA4CcG;UzMT5=~{u=r5*QY8jx@EekA1bD5-D+5|C3uKYWw2ma5cTNap3 +z2_;tjGT}y@Zh3)7?Xx8pIZU@avyVx1x+OnXhzDV*zjm&BCdjvZQ=QM2)A^X7A3=G3G}2v(X2vrwIu_3K9KF +z2;_&N@zSCz%d={=WYgJZ({yH$%?~V2Uc?=9%>qnx3=_Dhr$+;g=5WaUVaYm!bX1A0P`SBg;j~&^0%u~9 +z6y{mpcx>z018r}1LEHEOz^1#)Nh4h+PQm?$cqSG3kP^#wKz1w??xl~-BLkJsE4#iY +z`i%&_ULR#Y#T^p3U~%-Jc_Q9O*!iuNb`#YT0n;UeEb@eV)_)}6(wxC{Td+3zYd!N! +zdPbm&gjRKbzgq0?UcXvDV1IuhUq}1Zdfvl)o%M&UkIINx*V% +zufm$s?F9J%*OT9X4B5s2H38{0u;*BGZHDY;NQy&@o-6iZdcA-|=vgNJh&duU*j&W1 +zIsR72kNJ>}_k#2#xGC!*G9q|Vn`AxE +zcE^s9vr{I^0b|}Jq>8MW`@X0>Ee$5{DLX(2(H0etE*eaVxwPe7W^@hKWx^L1rU!!# +zIog3E8(9bZ&@bkm?)`(%g7reIx8vXRvmnF$LE#V4@pEU^)U@}z7LFv*2Zzq@XFQ$} +z7ve%n9%ejh3$LH?SPDE4DI-cj3`@+Oq&-+hyV^zIn&Dw0Q0f5m +zVZDnr!jF&YJvhi<{=}6o^MAvg=!t~)Qwg@r{I($mlQ1Gts;{yUMX!V}RXfRZUxa81 +z)?F<1fVRYv@zr;Yt}>TBv`Au3q{O!q?91qfG$z5AQ_ai@I;c*@*WOjk>-D+P0jNiF +z(O~szsB|AG%W2b6(~;PeEJ2l-&KS8xG!zH82$rHAmqPUx87V=)&uq9zvo%8fNO|{P +zjW?t@ZFyy?KG1Xgu9V=!--fmUA?G+dqsX)o&LIpNNf%0M!-h?In1l_VA)_e3u(`NM +z3Nmj2WmQG@(jpM>NtO8`KSI%&pD``4S%OD-1 +z>eL?Z_MS;zba2`mH)LcaG7&|03PYwRv*`!YpLbgHd{m7r=vaY@yVyy9OIYP%C-aC_ +zZ#2VZ>{Akj#ia3|zzDCgEW;U3Y6|*mNDtTdT@_&SNs~j|0?rFMV7O +z8HRY~bFUCtpW_4QSfa12eP=7p@=GP2BhQx-&$(2zo_;`|Tq^M#{XjhD(p~BJmR|f? +z^?sy|AbwSkRi5QwvSV>wM=m8d=#hZ7)I&>!s`xQWeL!fnf`}uLnWKe3vmcWSw7T{+ +z0;>2n1$g5qAjg*9fmjhJjeF!216hsK){p2L5%G-Pu2LQ1fyX}+O@oN*9I-@uHUmmW +zfztSA0KD4F6fwbp&bbcx*Ni*QEIcv-pEj2bENDN)&oEQy51DC^f&K}>Gck;FIFcc1 +z-uE>c0*q_bMp6JK?1O@?^)CXpK<f%J6die`d*YzOvnh`R1T}6JxE*O`3)xRA~^wt-sHGYz!v$PVs +zc!=f`QQp#kRwf(3wXP>AH0KcCyFsF^2|UhE({A)E+db#(RJ_w{_+NaaUL#Z5ioZVR +zFS;_sc>_O*YBv6gHb4Vm%5(C@k3NUBB1MUa&zlqM`6sKG{qI#Qeo-Smrf2-rRB3VMO93eHzYTGO7yw2;#prb_A|SgKdzTUFNe||5!aAPuPgWS_qRX-OMV^VL +z6@R&QyJ${%>witA!o5YKPLryv#_HBI8QnzNxvU@PsH>EGymzYl6VQFsC#N;Z$s$Qe +z2)t1*Nn&f`H~BWHHiMv|{E5(p*&vAkNyMX&(1@8atE>OvjVFzLyW~mJKH?yqHn%$S~ +zZ?^6KH~X6pibeJtwhbqbopNO +zs_d96>)Y0R5n&`9brY?Reqgr?6^5IQ5Ap&xZn9T&6C0Z+{%1Be7fTH8O0h59%P~Cu +zl-t*QSo*H)YkosAc>6XrZ#~QZ0ZQQe}}uAKQ1AZGWD@6!QFgh`0ZA`jroXuj6o=X +zVAg#xG5_MVw}2D&}kHfD19 +zW!sm;-x`0-aEZThHqlYWC)_OH8~`N@NQd8UM4kooEpav2VfcS0uvVH2wrFwBnwi;n +zP$GYQfi;|iE=u|b)<%VawZr!@2V8OZKIS_g58`T1(}J+6UxyOO&4(tYZ$SM)#PLk- +z;G`IFs*pgwbU*WIk=ewnm`Jrbp?5Ktu?c$wTV}w5a}dQVKNZXt=CVkcSnl!@U`wF( +zh&~#Qs$lpQYG^!(d|>|*n`baX2Q;2VaUXxLWi~B$aw$Vjup=z(T}7(O-gSQ8?oRo= +z?_4;Ytu~Fa>s!ni!s$1`%q*oL$i1S8gw#nF&?glv*4DPA*tskx6%fu5hQ3=E+H%LR +zxBh55#AJh*>Xsa~G~8e?@bHocuvHoNz-^=Q36ITnP~hwHHV+pv9= +zYqOXC^86Irpd0=VT~NSY{>zV*g3J{n7%J9&Sy+8Dqc{rXXDQYIEaoPZKk)VNGN$j= +zuMpX|feq4!?@PVFp{KQeu1wo_Xj=kvvgd2IDGx-AOOagPq{y4 +zNErGhd_)05`Y}T~XlBlk8f;HKdR#r=>wf_cUm90`3z8IY)^j~9t{%qU!npF3fX|`U +z|8`tCB?oNCa(k~fS}J~bppu$yBitj)XE02}|E*?O?4uz5cNKmLGR&00%l->(b$=6k +zv)OXBW|v6T&645Zo%U7?`LtbJW#my={bzw2E&BrS3%yZG&R`Bn;1Aqr;YGxe1d9n= +zIhjX;Nb!2-Ac5lLWYIh<@bKak9ViY0%1U>sI%ShhK4y&h939C98{3;zkS>3#T1}67 +zS04%;ZZDayH`GW>!<)t~*UjQzgU&k!-2ZpNrHR2tqI_2x4w%7tn|=E`bK&B9ZxYUh +zlYbv_@*gzQ_U-Q^T>Me*dRTvTY-MFV?2G$5i~IJI0EUEw|6B3ma6Z3ef2WQY%l^*( +z@#3_>?`_r)-(oqQNPi*raX?q7dSV_H~Kws8*Aqx5+5LU2PK)j37gH5r4*w+T~pi +zTJIR}4OoHw!zT-U$w7`_a!S`Vnyd8mLJ*qsy6E#O=Z5JS15EW{8ljvV!8cZdjI4kcgR<@9rk1T +z3%5_ob`$(hs&aG;XU4FXDOf8gxsdIXW6XvhQli%i4&Uz;?4L|rI91jQs-%vr7ktio +zK_w5NKGG@x8OORBMB2yS$VCm_EcGTibXI)3dCwZgYrX_+n=cWD+!zw7oHwx(I&}68 +zEkEFu{F`%tb0^-XK`mPS-^xe**BSft!GBMFwD-<$g+#7mxpl@1m^R{UY(AdMWXt}3 +zSpbdY_T`P_H{tt_5B)%J;40Qh&<}UOj9NeF;J_)8H^kO7Q?BkU?8N%7}UUz^UG3ch(gQL)KW>$J&psUJdrKo^!U| +z##%>z{HO=9971Cs^{1ifjMxlaqu%;3Uc`D(X?uf{z$L5Pas4gd~ta +z5y}sYP_ga4RN1R$>dCBa4pHY|H6%#w@kpf +zLs36%^R_SM*L+hJqqEeacL#47;s&gn`9&KVHpJTbIu;I1FTq^LD>RmD@@cZCp +zNhD$CSjSRT9OsQQy0292GOfzw?X*k%Mq0`kAQ`3EI@yGHLmMxXH`= +z#?4+PIh^9jX3UTyto`GWo4EPbs*j_2+kF`~=T~K&`&1pS3YGtDm*W^4mAm +zrYygaZsE@MPvuN6yh*0Nprr2a_nTdrK2&&#Ol +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 +z**h+I;l_VO-8TN^(|rb&f7Kk +z{;S@-@GlDwCEPvbjq{(Z`0~_;{QtQ8rCYj*>>m|sS@qoZSDWc2C<#c8q! +zw%)z_L{NML_5B{|_dVqKft2tA$)bji@85p_G!Po3X|SfG2s$BxPW&D^>3e9%52TZS +zAc?2A@PL?DT3%W^K3*DB8ciBTDmPKnahi_T)L)ZSos*;*on)%ia8tF0N7Qh)(})`H +zcKSUnaN_s1Kxb#sM=n=Xlv{TS=@6z``pL8+f_j>nNDq{mkwll7#|hvxFar2M0Ne`J +z0hfzo;EDoFv*aTPE~Z%AuyJ<=n7+7@fET2IRFH3VBNr3}xgnWl$TUPEefP}Go-v7? +zGE)*;`-8#YBycJ?1Dpd!fr~*Jm<9?!HK+#t98RQW +zlE`h4HP9MB&c|axB9?flxxUQh`cR8;%r)hUaM6?`O3eeiXtz?W%dN|Jt@9;Vcab&D +zO0mwjlCATsbFuntYq&Mmy3iV9jkYeJ1S4sIVb+hVv#c|%p_J@&S|!q+$owPii?qje +zlKbK(DDwrFmCp{7%^a_W%TwvPyl^;l>Qcs`r!cEFohqdFIVZfyyNZ-h6!9u9AX=dnmRgpN}A3HHEO +z^X`q+@Mic1cq@E6Jdqb@?1OuE^RTNA_Q7YvE8+58$d<-7zq7M_a +zzHF^e7L4C^=u_oK){5*-QKsyOn%Kt^mk~dU|C#Z##N-@;(up?~AMcHiSmQm3cSmJ- +zlJ0Vk_jntl$9smYi1B$+mc?dz#@6?m=xKGiuZ`;C8H>#48AhD(o+Q$Y_ry(-CRqvY +zL*Lt_hyiu1xrNi5uhM +z=n5pW;65aC;CY-+)aUMx#JUOHtn#_?zuyS-QOs6Zbyi(oGd{ +zNwMUos!hmbyti;mye?bXSbS?3VG^H9j)W!7CqVpA#-k^YOVbO9Y&x>X9J)nTjO-DI +zO!B)C*(zjbb8P!pvFZg9e~;^mby#))8piZ@63bEYRw>QCM@wTkd<5>DH$h@x$mxqTjQeRR}1RfD*x +z-ABg~cX~xQ{EEcQ^sID6J%}n_{8aVdu7}ygu`0vi1ri5J1(nE3k&Va4C6BwJCVG+@ +z#kW^P(;t?_WP4KTV{<&Kd%F`|o|H7tFtYFHBDUVA(NW9T{}3vKqz`UJ7R|VxNf~Xw +z%<7>JX4`$R&E>8VPwL(W$4hMu1xYk$G~@nMj%{C>EWR{dTq5a8PvRshLh?-|-X+94 +zO1;?mVr;j*ulj1Hr^)4RjqWjDdY4^}CiLyOiR(#6-^USk-|rktI`#dn&v)xE#?IW{->t6|eK{|(%HPo*T(+EJXO?5^TGj?Eyj-te;5z-1 +zJT6n^RpXr++at!}OsT)9HTont?jxyDxwcKuUC0dCDag-h^t>V6M>miJo#67 +zX8JsNY8-t4y{+gq*K-*&S0mej>?|>Wp@W9VkufQsQ<+2UBkVN^o7!<5j<{|rUrP6+ +zM$NR_lP#64MI(-UJmyqrbwp_?>yR55Zo|M)vHlxwKBAO)MxcV?9 +z%Z-mpgRS4VmBM+6wuUqt_%3Au$M&^@DlNC?NIB@0Hrzp+#GRe>CsF^8{&&?tik<-M +zlLm#PiwT`7z5>Z*LXi%aX_-*%lQx%OUnubdo6CKy5M4#liJB+raG56vnQ%T}Q6cB~ +zIzKZ;$n{@{;a1Tq9WKMTP^vzc>za`C9U-|qD{8Zuj;8vrNMxK5VyIK3=3*Re*8(G# +zabC#SsWyCaj>{`D`LQiR*}qE1XTym)H}$wrpV#Vee%ztLO*yr?|7Xmr+aWT;F}i&x +zXnv*UMVgOovr|-#j7({t@B4$zGt<&8@(xQ6&MgU)d&ee^N=`X%%y9dl=Y$Ta8}A!y +z8mx;vs>44VV%pkm8NcW%kG72ebd|?grX9MkaUK5BRW1XfQ(_;x +zd9vc|Q66tq_9*Xb)%7Uv*U|5sxwvKRZM9Qk@=dUsn|qQcT9q&NBZhBeUKb$e`V>i#8thS!ygDm4;&QK(AtuN*@z%_`;HrzRGRV|oDEujOU{RnG*F$)}!O;#Ys<1nO@yYowNY#Q;d|QG~LM +zT*t3dQg&2m`6?sV<9ZEpWzSGMqPpMbuu;d4%=c*>-;6&ETK@(We~^{hW(&-C{WkKR +z>QnZyx_APb7ZG1xCoA%-2)R5@>`4!AsOwRFHge@xGGT)%ZDw7h<=)Y@K-~j#I7iEQ +zXsSrsPe^t}O8qzMdd{=O)-^hQtB$`w%WJeePaG9e_rLJlw0zZACAJ>2ne{6zPaS6q +z)O{_7FCdqEQ=iijsTU!6W37~{=@R>Vs?DrVwS1qJtNT>Kj%YpJzt|CD^@aI(xYGZU +zEk9Z7IakZ;UbF>juFqkrmN%`n<)`TQS0h*clVJ>o;iX#Mw9J;P`%Dh6H1Y4YrYL#$>+B5VGX58~*@j@AqU*Jue65F5 +zMR#d=%5Us*bx+OV<4RBW>)l^;eD#AO;tjTwS=+SS()F*0Z|fT)k0{qb3Mlo^rsWA* +z&uA@A{FN_z{6a0aerpTP((+rhyjkl%PRs8{?&@JaAoD6^&trB( +zqkpZIr)WJXTF(wGuhZ>l>iLM4@6+*hcePIGB?-IFb4DN+f6H&viZ#1b$-B=(rXd$Q +zQ*?RNJw9nlwY*a2iy4Xz;lQ#dG;+Rovx)zx9gk*G^fTngOS|X_GxgA>BkI{!n0UX^=uj#uL4Pn$l255SzYb6ScoISYGX~ +z3|5uZgaWcPJ9*^DaVe~F+M$vWk=P(k+G?w!sHi#^GI<$kX-Nnzk~?t_p43rb4xH}L9nV?wQYx@ZV@|mDD`Znx2={G((r!WN(B|wj{JhPp{jtG +zJ1OiaZ))61Avrhe_>*T0MDYNkD| +zm<%Gq?1plxi)dwMkf}*k!~XPHlYP0_Y0d}{X(J4^4e2aLS5xLu3}YlCIo*0eu+*ts +zU4XhZmfxQ_d4@kDOZ!U}U!(Q=r{|_=&+%vGOd0RX@lTmBVOqv?|8(E@oQy91)TQ>F +z+1cZ(pQ_2We?FwfVJ_RjU}|b+PWJdT|H$N#$)l}MaaF~_&Yvv#v!~~BF*36UDoExI +z)7AAB=fQ`%CjWtDQy%gIbx3>v(8@7&?Dx+r!>71haar?Ql(?XBQD=!qTwvMF-6@hf +z{2vy;w2Z0*vMQ@s5YafQHF|6dzrUb1AVU;=ZE-{@E13&2QzUq(%I3x9sJtQ+OrBR> +zldR@b=am#%>Z~|WU2G*6E-ELb%|lgoh+LHAw`?k!KJgP)6)X)%0)14;q$^pqL^6lT +z^SGWShq&}v$ue~$Qic3{%}Ndy>&aqqAxg}doz$K++9}L2IRwf|3dp`9B$_c!>6uH1 +zNTyGfF&#DY-yg_jU58s0?s;r7&*=>xs^x~6`!;i)%mHr%sxayE^?~83IwFrz6q)(I +zK4o8_y?4^2U!@NWUnvg~Ajww_P5LAeLa9J>oAhRV!0=QPKpY{h&OYIP7Lc_a6V0q6 +z7=F5rY3w)g44*-`tQDDav)*91x&Mu{U#qnWIgl`u-mFU)Zq}zv{*mP`*XhS=g=W3W +z@O&+o^xPeHt3P@EC3Rx%e>?7b6A?n@d4+vu+iH%5R|98yuTE!po-;DXI=F-19Fg9v +zn;34&Yt)!?!*7pBU#BxL{NOK5N}HSfjkGZ$y;=V>oZHSWByHuSdpVKvoA&EiuUT!! +zYL3XzYl}Pn|AHfD`sQXk$Z**c>!is1e@mbqLpK +z+c6^Jn{&hEd5t`aGU?5FQqsSf{#DY7@0s*w-N~!dH|z47a+`cjwcbWriDJ^5^{KWu +z5JFz{zw@6-Z}{5;In!HhI&Q1YWqfjyNoV-`1Ul1?ZL?#K)#-;h6LW0R8SzdnGxq1V +z*&+ElePn!74<`L?4kZ5+BhcKWH|z2ySmFvfd57=XLF#{<&cC*=EtK{C8er@(NzDWkm|OHp0wH4w7_34-tboV_ +zfhGflCajj0e$|dGpVvP02FJEk5yGQ9u6h~4qM*R|Xqf=k^ebM`(w@8a*=vWx$o{@L +z|FeH<@3r6O?6YUzPffkAn|iH@IZO`@Axz!XS2)c%UlmcQjN%Gn3pd&L1%pk$4r_h6 +zh}ZW$pI+}FTZCRu5L~L!_Xsqp8-!imAr(x@q(q`NqP87St(>wwEYkQRbPBI1hAB{B +za?7j+hg(f^rmYL*#;Z4H#_s)B)b@>?pS}Crx~V65o<#id=0@Q!8h;kWHrw|M>$Evs +zozJ)--aIWc3vn*~CgP8WeEb#QuMmGz;34zO=v|wFdme*FUWPs9lIZc#zm78xu519O +zDIJE)CCV1aiYitfhyTFqe!Ndp{-j@f^y?aag-}!p^skMAWJ{x$`^G?>%YI{KEQ!Ul +zl6iv12w|Nj!QB-CJk6Vc!Ae{$5%GmW*edKL?uU^d-Ygz|N4&pL#J#mb8SfvSp)*ea +z+5T#(kj)|Z86nI~oH+&fRh-aQP`IBU?=TE=F&z(nD!7+;slZjHc;=ukl?Bh1HN9J;JWCly +zQHwij1Q>_uc-ThiZPc=Mf!P;SA@_6vCSaV0sl+?@ka_VZ$80Vcf;5)Pqj+-0PRR;U +ziytFLVOemINOz4Y#E9#27EM-RX2;1tn_ +z;|=_VfhQPvG!@KyO;!r}yP~}5y&}~}lr%#In}JhT>(+b&r?J<0iGfq+=)BUvX;SLk +zA-I?Ef-O1{7ndQ!WCM2_c)o$pH*ns2GI$I;OG4Ck16TDqPsR>|fmcd~H3lx%CAW1N +zc(zQ8+HK%+*txmaz++{4J_mkcFpQH1RKJ1CY0b?q8n|3foc9|zJpnmp`wTo&C*m@I +zT%UuywK)dE2}6cS1|BqUTo%LOeFGmqOc@Iq_!t8Z-{NH~*+%*Z7=6!Es|HF(m`UZXLbK_ixE?ba4w}9u#{PAKgtE);d|F(c>;tG>8Hn1u` +zOEJYcR!egRrac3DZZHVf19L;(?yY&nOo_onlu~z%^y3k^~=2X-dz(rfGw}{I{k# +zeaEgIa2~j*Y1-A_hqIvJu0ACJF5cx)UWS=P0n^u?!1bazN+HZDeny!D$BGXq?Xacf +z)2zpFJbdR5@)hjr8NCc+uAYjeToP(eln#W?z}C{dQ7?sf5d*Fs%O-fObixQ5CI~Nr +zOQnmmpTik5?7*@eW9VQn7vbRR5VVz*#yp80&=AUj=gKli+>Q3|dN@?pnlXSg8cY5i +zmxOPfz%*C!7E8{7>hcQZCFm&6Oq+X2(|qR}e1B{3X|Ch7xs|-@!pmS!d1-VVE?!qp +zCW8y*4=UYIG1Xo9F!sZifz_X2mABRA#^G(MtEcANSjK977!UvWf~M7S%K;lU9DNEL +zgg2(nu#LgYqOD({=X&Mn;NF{>)*HSLGhyxafVJY$Ag?aW0)6`5@S)M+&Ay*Ag!`!~}u82-?!yER}=>NsG +zVyAS$|JlE-L_vM!x)FEd(>uHxPF7YGoKNM}l=o1?$S2eWv1z540t=dPUeg*Ee}*6l +zCRgPsMKHH2XVOp4@fIg+Nx;dA^I(Bq&5C5K_?*SLFW%Qv`xveYOB?L2%2UR}!K(bR +zmiMtEo1D!&*RHQMt-<%PK7wC{e*>oKdrUXphkL8Dt5)Me#>%WjF;*soajeW)bkex^ +zQ`ffDxwhB%X2IqAywP_VYw#gFU!9>Gg8kLDJR7F+9p(k~;5!G6r!aSeaszw45`S(L +z(Kt}1=P571r0MmsefUuE-JhEuJ_75eS2SLDkGE-mF5kH7QzHB(5`BgYUb-O%xb7^D +zYSfo9TFIkdePfVU!p-U96r3e9HZHvflQiHwQyYDM8ch3_riFt@L`C1wSDd94Ya%L| +zjurLrieAG^tO?|DtrI#4@MySckJG|i8yqP~h6LcPi32Q)1mN5+9#f*Lxc +zX?&z?hc)eos2_!+BMFPVL4`elXaZH1i1GIqFzTWIjLz=qVPTX +zwx+E|?MLlJEjg`eAyfzIb<_b=w*~EIuprbR_VW$YAsozF+)@N_vh<>M;w-$5>cOSr +zz;~wlS550j^`O2r5?_=-ya^%bz&FHA)FD(AcUzvTnwF2+i#h}TIDe8-0w2wvWDR3a +zsn{_x@K@nN{DmN^DJ^CG>zXF_8?{Kg@pnHoHq}*ph?a>MW8aEbzi(O?kFNFC=rzOdg$C3p?B!@U*)+Fc{y|NnZxVga+m4pa$jT`-ug!X7{orgKld>Nz?PS5{0o#L? +zbXe9rR+#}?nl~xY5VPPu<+sqYpg3kK+E3tUyo)ir>E=;5w%}e+o1?7!<7^y`glr10 +zP#dyzNLg5?r+5`(E*u+~Vm^0;tQM9me9)4MBh(Iu7S6NqF=SwExzF+z-UVSU_ +zj?o`$bJ_L-#!JroM_Sj7` +zn|N39iKb;C<$>o=aWXUnUTU2jKU;nw$S(ryQa$jm5FUm%ZZqMtlsl|))7SW&F_p=reglOqn+ +zt>{9hPE)G4#3m9rNZq6!(hky2(q2+OX+P-zX^=ETIz-B{m>yJ8n@}$+A>bf&lX^%y +zNIOY;N&Te#qywZu(h%tosr&{L)nKe{)~sq$m3Zk=T7Mpn4Bc~u3jcF +z^C{?&v0G}!BPe;Gb&K-$C9$Af_6CEQjT!#lHDFAi%;fhjaZ$b*_we!@?e+|*On&d) +z&T)g%Yb4^{%vWUJow$|R?#SWykC%HR`iOreoAB&K&Ar_dp9GxC1u3m|yixGHLYAPA +z;a;78qsiliQzNkDyaf(&MD=IMI#-$JVUfOs+~pDXFTI^l +z)45k=4l;Ns13zR_aFIBc2rxjrg*c=14&r=M!_(U$3f@RO6jAU_$=lEYUm6~A^h;@2 +zEWiNq0C7LhfWe_(89PGUL+NFUFUs^0{Bz>$JHi2fw}_=_5-k`YgIshfaa#np5%)hN +z9C?JWQppL(3b}}fh|BcNCN@%Z^+Ry@$zp3j_>m*JLC=PBH2|?|ij_X7UQ=WmS^DEcq+7W!3;+xe* +zDW#aj*{;n2_pB{4F(<5AyU4`m!0ENc^|>OE+~K@NX**^L-cLS)(npa1@)Y$lad!pG +zURRXSd$%yl(_lMxAU{?fD1e>o7G*>p3jdtU?&%Qw)VF@nw+sbXxWtvPS+#(@vZf24+9)GsW1a +zvBgDY<#v1EL+=Y_=!?yO?oD;DI#vz5x#?|_G6cK7Uz`}idB%g(b~uMr?k=U?0Bct< +zba$!BB53L=j`7o;)en7L_LvaGLhug8pAGsBZuAmR)X=g2w!IUy_<5GRVzR6EyjXW94&X#yr^Rv&VSIsRvH` +z>?%Fby|{wK!&n$f<6Com=fPmc9W_--_8bK+-mQ939Dr_@fs- +ziW&p$+p=PMso+k?>Pm$dk;n(oMmW9AE@SFONZLL#dZS2P2=8F_N4Mum;DX)T?J?aw +V!n_vFZMVlN^j;mUKt|X2{|D$~Sdst$ + +delta 7376 +zcmZvh3v^V~xySdJ$%G`tWWp;CLNmbx0w_tukOaZZkVpmyG87O*9@86C9wCHY^;&8( +z69~#cvsU1vf(RN5_ex#1H=0$86mdW*c)eU>X?@^>v2bO=qlDYl3W}cl-)Db29R~MW +zXa0NtzWsikefG5f-nM_Itvx$qps<`@Qk4!*{c$^ukYSV`|Kt5yrh@tO3Og<*%Ilhy~J2V +zFC=7=J0Z)MZuy$v+a`U+ZtM3!>(@u`C)65uTQN!;m4R$z49C4I*4u>`Aku`hTJdBh +zqQR!Pn=AG+*#@|5=1!Lt6bqGzAETROK9R`^(B)=FI6}o=E5TrbPQ5Nw>ZQt3(KyDdqRmY +zIiubx1s-GmJKaO06}ZH_9xC(i;Z{E{&W$MQWcpvesgw~8~0>Vkiu_(g;*$6RoQ +zcq)nKxqt|d@luX{ow=`Afli3k#s(fs-`&cvgA2&u0y=q46n1%%erluclze(FIgwu8 +zn%_!lZj7dpX83LHvfk6uX;Gp=RS(zyxuKaB!=^g-Qv-pm|%HijRsjh_0tgly1K(UP@*e +zy=}LI5c0N_Jt-x<=6Gp1`Zs#XQ`rARL>3YDwmJ6DDNkOa8xf+-WP0j{AHrPd%fRt6 +zLdf8KDJp}UFQ|2LY4UdD0D~x?Lz9OkE`~k2i@un=BqxHoijw~_TM)LuO-bMKXL|D-dt0V3hxka+oWqlg{TQ39R1yArdcB`hg@hl@>IB&KC7AN8i&NHt=X8( +zQ;nlNuUt3H_UK9^LhX)H*zl?Q1|MWxYs~yi#`jlLwlx~~y`k;kaa?!1yV7O9IwJ9>xO4p|NbB<+M{c%u= +z>W>r{(I2DfUu(;3bB58D>a3x4191%jI^{(g;{BmfK+C;r +z6BlD&j&7qKZ{?`3he+$-uOMpV(`&lXv{{IOf)<`K%^CHV0PdjaQ*SlyrPWh!9eLnm +z*{A6n(lL244}NM@v+PdP?cVx=ebd`&R$&!5TIuzvg*fA#o?0}t@FR5O{hkFf)ze>_ +zW?kqn)(CzTeV2w$n{3Pam>!xoymA|sC@QlB5?dL8%Aqp@-89Hs +zD7P-xz$~e2dFWpdr4IAVIV1Fkp3HAeGujKHD*A@C;v50gL{&5k74^%CrUFehj(O5Y +zEfV;7(AhHvlMJ(@0=r+d@y{rB1o8sNlVB0KX53M6c>uhH($@NSUBiF;3Bmuori15X +z@IzGCny+fgpQKGQhI;!UU3y*+H4=YI|H0Rr9Vrh9u_65E=-K<_#*qHY(s1aO|9cQ@ +z(nb6pb9{+k%kqox18=40)ySdRFsZxi(u_=-v9HVFOR?Jq-%S~_3yc@L#?St*RPsAr +zulo1)3&GR-6mj~nWOMh=^1 +z6V!FYG+Uv5=niNQ`ZBZ^8itmpD@h^ +zsOyiWc~FL-SvXO&pF}~>PV`Y2+KmxR#R;JkGh{y0kEwSM>c;v=!W*aef6)-A`%BZ@ +z2JM9IgZl6mINA>@tQ($>WP}T**$ZugCeS;}MjCFqvTS7bRrE~kfdaF^IzabQ>GI6M +z%YJW~`uKou7aQ>`rA5nY%Pzt)0wHlvn)6B9!Zdk4lW8wXcF;5nrLwDoCZJcd=;P%x +zM=g8PG}W6_9c7lmvJGq;$CkBu0xV3kRty~c->~4#XgO>GLXJ`q{ba>ZqmF*DV!e?? +z*(;|T2Wj`p;^aGFKaSo%i%_{E^$(`mNRt{yZf(d-mR0bf|8JwphT7!$2z4N|kajoB +zb6kfnNY@%>JLX^{vS`+->5jedMd^iAwT@o+4$`GnwaF7P9=`WYvy-l@Dxe=VmODB? +zcG2#}TE|8BN~ouCWndvjr|EO7XOtmkra9N!>eG^@yk(u*9*1o&Y)hpLPB!T0U;-~3 +z>s!E^=Cf}yQqqz<{be`ogRK>>vd7?;PIj;y{a!Hr(IUBw`zLVk8FPr4FA1E4QNX2V +zFos>ToJ-QC>65-b*`qHlw--GiU8)k57JhwR%B@xgY`>OsHq(OUJfoS`HjmAB%Atot +zQI`9NGn20=>ALBiW*5#B=bOi7xH(5mM9T$fRG=6$@%})bEz+OX2A)Eu9@~&WiL5=G +zo7KnaXKi8)vbM8!uy(SBS-V*yti7z(RRzj%W?01um(qYJVc=%7=Bj)&65_MC67lDyv<7 +zS_WfdYK~>w@_js8xW1RuYwO1a;`&H_|Jra{;*@JT#>v$yxn82$R1S|J{#k-vx>GEE +zQs&U-&+v_~6eD-dSh#k@l%fOA{F +zLcGP?&+&D^pX>N>{4#U#LzRJi+o*MiceZSCm<{b*fRni^j=Pw5%u*5k9KTd^p8~o< +zUgikHjcIJUqUN3jt%IojY}54 +zwc_5qF&we@%^T0V;PRe4e#iTq^KrK5oRyxpc;ALJrqB8yUb{AdcdfGzDGwI3yp6@F +zWO8V%EmA^14lTAtCea^5#Z!6D(I-23Q7Gfa-dD;@x|zH1{Vkoo4?V}+T}ab5jm?R_ +z*DXf}%Iw0~v~|(v>O^yP5ah(=z{ndHjv_1?Ej|6;Yqr +z5-?~3I4y*h{5&Jz=6l0S%|Tzm|Fo!4MTivgv}Fz-fm53tf%v~n+{{Hz*V?v_EqQ!}s2_XBxbc-;sqOz^#|?Hr +zIzdxtRZ4o-_-zN|CDWCsi!=1;Mr#F?ZZFQzhe@s7v<6h4b+j&^SGE@?yZN~4rk?HP +z$w3a`NV%gtSsygrw0uWJvR)fbdI>fTxtw$!Aw3QQX!J9EA?E;E@l3gMplZK9-&E2o +z&lIQVrJq|(=aGW<6yc-MJIkHC`Riq!OKWx(575UKt=ALj)t$x3%_?_iBc0z_u0vUk +zH2T@TkgJhaJX@a1r<2HPIZ&R2ffRl=KRKw9izYgc1bV+%L!)=~g`8_>#jYx24ZXCh +dDrt?1pG8*?VgAolXcm?3F1P)vl~(K?@&8}0C|Cdh + +diff --git a/protocol/src/main/java/net/md_5/bungee/protocol/DefinedPacket.java b/protocol/src/main/java/net/md_5/bungee/protocol/DefinedPacket.java +index f33ce913..01b208ed 100644 +--- a/protocol/src/main/java/net/md_5/bungee/protocol/DefinedPacket.java ++++ b/protocol/src/main/java/net/md_5/bungee/protocol/DefinedPacket.java +@@ -30,6 +30,22 @@ public abstract class DefinedPacket + writeString( s, buf, Short.MAX_VALUE ); + } + ++ //Velocity start ++ private static final int[] VARINT_EXACT_BYTE_LENGTHS = new int[33]; ++ ++ static { ++ for (int i = 0; i <= 32; ++i) { ++ VARINT_EXACT_BYTE_LENGTHS[i] = (int) Math.ceil((31d - (i - 1)) / 7d); ++ } ++ VARINT_EXACT_BYTE_LENGTHS[32] = 1; // Special case for the number 0. ++ } ++ ++ public static int varIntBytes(int value) { ++ return VARINT_EXACT_BYTE_LENGTHS[Integer.numberOfLeadingZeros(value)]; ++ } ++ //Velocity end ++ ++ + public static void writeString(String s, ByteBuf buf, int maxLength) + { + if ( s.length() > maxLength ) +@@ -198,6 +214,12 @@ public abstract class DefinedPacket + } + } + ++ public static void write21BitVarInt(ByteBuf buf, int value) { ++ // See https://steinborn.me/posts/performance/how-fast-can-you-write-a-varint/ ++ int w = (value & 0x7F | 0x80) << 16 | ((value >>> 7) & 0x7F | 0x80) << 8 | (value >>> 14); ++ buf.writeMedium(w); ++ } ++ + public static int readVarShort(ByteBuf buf) + { + int low = buf.readUnsignedShort(); +diff --git a/proxy/pom.xml b/proxy/pom.xml +index 10fe411d..f6daaea4 100644 +--- a/proxy/pom.xml ++++ b/proxy/pom.xml +@@ -165,6 +165,12 @@ + ${project.version} + compile + ++ ++ io.github.waterfallmc ++ waterfall-native ++ ${project.version} ++ compile ++ + + io.github.waterfallmc + waterfall-module-cmd-alert +diff --git a/proxy/src/main/java/net/md_5/bungee/BungeeCord.java b/proxy/src/main/java/net/md_5/bungee/BungeeCord.java +index 4c6d9eb9..d91c2819 100644 +--- a/proxy/src/main/java/net/md_5/bungee/BungeeCord.java ++++ b/proxy/src/main/java/net/md_5/bungee/BungeeCord.java +@@ -17,6 +17,7 @@ import dev._2lstudios.flamecord.commands.FlameCordCommand; + import dev._2lstudios.flamecord.configuration.ModulesConfiguration; + import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + import io.github.waterfallmc.waterfall.conf.WaterfallConfiguration; ++import dev._2lstudios.flamecord.natives.Natives; + import io.github.waterfallmc.waterfall.event.ProxyExceptionEvent; + import io.github.waterfallmc.waterfall.exception.ProxyPluginEnableDisableException; + import io.netty.bootstrap.ServerBootstrap; +@@ -256,23 +257,7 @@ public class BungeeCord extends ProxyServer + + pluginManager = new PluginManager( this ); + +- if ( !Boolean.getBoolean( "net.md_5.bungee.native.disable" ) ) +- { +- if ( EncryptionUtil.nativeFactory.load() ) +- { +- logger.info( "Using mbed TLS based native cipher." ); +- } else +- { +- logger.info( "Using standard Java JCE cipher." ); +- } +- if ( CompressFactory.zlib.load() ) +- { +- logger.info( "Using zlib based native compressor." ); +- } else +- { +- logger.info( "Using standard Java compressor." ); +- } +- } ++ logger.log(Level.INFO, "FlameCord is using " + Natives.compress.getLoadedVariant() + " compression and " + Natives.cipher.getLoadedVariant() + " ciphers"); + } + + /** +diff --git a/proxy/src/main/java/net/md_5/bungee/compress/PacketCompressor.java b/proxy/src/main/java/net/md_5/bungee/compress/PacketCompressor.java +index d07cf462..24184796 100644 +--- a/proxy/src/main/java/net/md_5/bungee/compress/PacketCompressor.java ++++ b/proxy/src/main/java/net/md_5/bungee/compress/PacketCompressor.java +@@ -1,45 +1,84 @@ + package net.md_5.bungee.compress; + ++import dev._2lstudios.flamecord.natives.MoreByteBufUtils; ++import dev._2lstudios.flamecord.natives.compress.Compressor; + import io.netty.buffer.ByteBuf; ++import io.netty.buffer.ByteBufAllocator; + import io.netty.channel.ChannelHandlerContext; + import io.netty.handler.codec.MessageToByteEncoder; ++ ++import java.util.zip.DataFormatException; + import java.util.zip.Deflater; + import lombok.Setter; + import net.md_5.bungee.jni.zlib.BungeeZlib; + import net.md_5.bungee.protocol.DefinedPacket; + +-public class PacketCompressor extends MessageToByteEncoder +-{ ++public class PacketCompressor extends MessageToByteEncoder { ++ ++ private final Compressor compressor; + +- private final BungeeZlib zlib = CompressFactory.zlib.newInstance(); + @Setter + private int threshold = 256; + +- @Override +- public void handlerAdded(ChannelHandlerContext ctx) throws Exception +- { +- zlib.init( true, Deflater.DEFAULT_COMPRESSION ); ++ public PacketCompressor(Compressor compressor) { ++ this.compressor = compressor; + } + + @Override +- public void handlerRemoved(ChannelHandlerContext ctx) throws Exception +- { +- zlib.free(); ++ public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { ++ compressor.close(); + } + + @Override +- protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception +- { ++ protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception { + int origSize = msg.readableBytes(); +- if ( origSize < threshold ) +- { +- DefinedPacket.writeVarInt( 0, out ); +- out.writeBytes( msg ); +- } else +- { +- DefinedPacket.writeVarInt( origSize, out ); +- +- zlib.process( msg, out ); ++ if (origSize < threshold) { ++ // Under the threshold, there is nothing to do. ++ DefinedPacket.writeVarInt(origSize + 1, out); ++ DefinedPacket.writeVarInt(0, out); ++ out.writeBytes(msg); ++ return; ++ } ++ ++ int uncompressed = msg.readableBytes(); ++ ++ DefinedPacket.write21BitVarInt(out, 0); // Dummy packet length ++ DefinedPacket.writeVarInt(uncompressed, out); ++ ByteBuf compatibleIn = MoreByteBufUtils.ensureCompatible(ctx.alloc(), compressor, msg); ++ ++ int startCompressed = out.writerIndex(); ++ try { ++ compressor.deflate(compatibleIn, out); ++ } finally { ++ compatibleIn.release(); ++ } ++ int compressedLength = out.writerIndex() - startCompressed; ++ if (compressedLength >= 1 << 21) { ++ throw new DataFormatException("The server sent a very large (over 2MiB compressed) packet."); + } ++ ++ int writerIndex = out.writerIndex(); ++ int packetLength = out.readableBytes() - 3; ++ out.writerIndex(0); ++ DefinedPacket.write21BitVarInt(out, packetLength); // Rewrite packet length ++ out.writerIndex(writerIndex); ++ } ++ ++ @Override ++ protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, ByteBuf msg, boolean preferDirect) throws Exception { ++ return allocateByteBufForCompression(ctx.alloc(), msg, threshold); ++ } ++ ++ public static ByteBuf allocateByteBufForCompression(ByteBufAllocator allocator, ByteBuf msg, int threshold) { ++ int uncompressed = msg.readableBytes(); ++ if (uncompressed < threshold) { ++ int finalBufferSize = uncompressed + 1; ++ finalBufferSize += DefinedPacket.varIntBytes(finalBufferSize); ++ return allocator.directBuffer(finalBufferSize); ++ } ++ ++ // (maximum data length after compression) + packet length varint + uncompressed data varint ++ int initialBufferSize = (uncompressed - 1) + 3 + DefinedPacket.varIntBytes(uncompressed); ++ return allocator.directBuffer(initialBufferSize); + } + } +diff --git a/proxy/src/main/java/net/md_5/bungee/compress/PacketDecompressor.java b/proxy/src/main/java/net/md_5/bungee/compress/PacketDecompressor.java +index eaedf4bc..066bdafc 100644 +--- a/proxy/src/main/java/net/md_5/bungee/compress/PacketDecompressor.java ++++ b/proxy/src/main/java/net/md_5/bungee/compress/PacketDecompressor.java +@@ -1,5 +1,6 @@ + package net.md_5.bungee.compress; + ++import dev._2lstudios.flamecord.natives.compress.Compressor; + import lombok.*; + + import com.google.common.base.Preconditions; +@@ -8,53 +9,75 @@ import io.netty.channel.ChannelHandlerContext; + import io.netty.handler.codec.MessageToMessageDecoder; + import java.util.List; + import net.md_5.bungee.jni.zlib.BungeeZlib; ++import net.md_5.bungee.protocol.BadPacketException; + import net.md_5.bungee.protocol.DefinedPacket; + ++import static dev._2lstudios.flamecord.natives.MoreByteBufUtils.ensureCompatible; ++ ++ + @RequiredArgsConstructor +-public class PacketDecompressor extends MessageToMessageDecoder +-{ ++public class PacketDecompressor extends MessageToMessageDecoder { ++ private static final int VANILLA_MAXIMUM_UNCOMPRESSED_SIZE = 8 * 1024 * 1024; // 8MiB ++ private static final int MAXIMUM_UNCOMPRESSED_SIZE_WHILE_CHECKING = ((100 * 4) + Short.MAX_VALUE) + 5 + 5; //((100 chars channel tag) + max data size) + string varint + packet id varint + +- private final int compressionThreshold; +- private final BungeeZlib zlib = CompressFactory.zlib.newInstance(); ++ @Setter ++ private boolean checking = false; + +- @Override +- public void handlerAdded(ChannelHandlerContext ctx) throws Exception +- { +- zlib.init( false, 0 ); +- } ++ private final Compressor compressor; ++ private final int threshold; + + @Override +- public void handlerRemoved(ChannelHandlerContext ctx) throws Exception +- { +- zlib.free(); ++ public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { ++ compressor.close(); + } + + @Override +- protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception +- { +- int size = DefinedPacket.readVarInt( in ); +- if ( size == 0 ) +- { +- out.add( in.retain() ); +- } else +- { +- Preconditions.checkArgument( size >= compressionThreshold, "Decompressed size %s less than compression threshold %s", size, compressionThreshold); +- ByteBuf decompressed = ctx.alloc().directBuffer(); +- +- try +- { +- zlib.process( in, decompressed ); +- Preconditions.checkArgument( decompressed.readableBytes() == size, "Decompressed size %s is not equal to actual decompressed bytes", size, decompressed.readableBytes()); +- +- out.add( decompressed ); +- decompressed = null; +- } finally +- { +- if ( decompressed != null ) +- { +- decompressed.release(); +- } +- } ++ protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { ++ int size = DefinedPacket.readVarInt(in); ++ if (size == 0) { ++ out.add(in.retain()); ++ return; ++ } ++ ++ //Velocity start ++ if (threshold != -1 && size < threshold) { ++ throw new BadPacketException( ++ "Uncompressed size " + size + " is less than threshold " + threshold ++ ); ++ } ++ ++ if (size > VANILLA_MAXIMUM_UNCOMPRESSED_SIZE) { ++ throw new BadPacketException("" + ++ "Uncompressed size " + size + " exceeds threshold of " + VANILLA_MAXIMUM_UNCOMPRESSED_SIZE ++ ); ++ } ++ ++ if (checking && size > MAXIMUM_UNCOMPRESSED_SIZE_WHILE_CHECKING) { ++ throw new BadPacketException( ++ "Uncompressed size " + size + " exceeds threshold of " + MAXIMUM_UNCOMPRESSED_SIZE_WHILE_CHECKING + " (While checking)" ++ ); ++ } ++ //Velocity end ++ ++ ByteBuf compatibleIn = ensureCompatible(ctx.alloc(), compressor, in); ++ ByteBuf uncompressed; ++ if (checking) { ++ uncompressed = ctx.alloc().directBuffer(size, size); ++ } ++ else { ++ uncompressed = ctx.alloc().directBuffer(size); ++ } ++ ++ try { ++ compressor.inflate(compatibleIn, uncompressed, size); ++ out.add(uncompressed); ++ } ++ catch (Exception e) { ++ uncompressed.release(); ++ throw e; ++ } ++ finally { ++ compatibleIn.release(); + } + } +-} ++} +\ No newline at end of file +diff --git a/proxy/src/main/java/net/md_5/bungee/forge/ForgeUtils.java b/proxy/src/main/java/net/md_5/bungee/forge/ForgeUtils.java +index 8d4439ab..79b4eec5 100644 +--- a/proxy/src/main/java/net/md_5/bungee/forge/ForgeUtils.java ++++ b/proxy/src/main/java/net/md_5/bungee/forge/ForgeUtils.java +@@ -44,8 +44,8 @@ public class ForgeUtils + if ( discriminator == 2 ) // ModList + { + ByteBuf buffer = payload.slice(); +- int modCount = DefinedPacket.readVarInt( buffer, 2 ); +- for ( int i = 0; i < modCount; i++ ) ++ int modCount = DefinedPacket.readVarInt( buffer); //FlameCord - Remove length check for compression ++ for ( int i = 0; i < modCount; i++ ) + { + modTags.put( DefinedPacket.readString( buffer ), DefinedPacket.readString( buffer ) ); + } +diff --git a/proxy/src/main/java/net/md_5/bungee/netty/ChannelWrapper.java b/proxy/src/main/java/net/md_5/bungee/netty/ChannelWrapper.java +index b98ee6a7..39149f21 100644 +--- a/proxy/src/main/java/net/md_5/bungee/netty/ChannelWrapper.java ++++ b/proxy/src/main/java/net/md_5/bungee/netty/ChannelWrapper.java +@@ -1,6 +1,10 @@ + package net.md_5.bungee.netty; + + import com.google.common.base.Preconditions; ++import dev._2lstudios.flamecord.FlameCord; ++import dev._2lstudios.flamecord.configuration.FlameCordConfiguration; ++import dev._2lstudios.flamecord.natives.Natives; ++import dev._2lstudios.flamecord.natives.compress.Compressor; + import io.netty.channel.Channel; + import io.netty.channel.ChannelFutureListener; + import io.netty.channel.ChannelHandler; +@@ -145,32 +149,30 @@ public class ChannelWrapper + return ch; + } + +- public void setCompressionThreshold(int compressionThreshold) +- { +- // FlameCord - Use pipeline to reduce redundancy +- final ChannelPipeline pipeline = ch.pipeline(); +- if ( pipeline.get( PacketCompressor.class ) == null && compressionThreshold != -1 ) +- { +- addBefore( PipelineUtils.PACKET_ENCODER, "compress", new PacketCompressor() ); ++ public void setCompressionThreshold(int compressionThreshold) { ++ Compressor compressor = Natives.getCompressorFactory().create(FlameCord.getInstance().getFlameCordConfiguration().getCompressionLevel()); ++ ++ if (ch.pipeline().get(PacketCompressor.class) == null && compressionThreshold != -1) { ++ addBefore(PipelineUtils.PACKET_ENCODER, "compress", new PacketCompressor(compressor)); ++ ch.pipeline().remove(PipelineUtils.FRAME_PREPENDER); //NullCordX + } +- if ( compressionThreshold != -1 ) +- { +- pipeline.get( PacketCompressor.class ).setThreshold( compressionThreshold ); +- } else +- { +- pipeline.remove( "compress" ); ++ if (compressionThreshold != -1) { ++ ch.pipeline().get(PacketCompressor.class).setThreshold(compressionThreshold); ++ } else { ++ ch.pipeline().remove("compress"); ++ addBefore(PipelineUtils.FRAME_PREPENDER, PipelineUtils.PACKET_ENCODER, PipelineUtils.framePrepender); //NullCordX + } + +- if ( pipeline.get( PacketDecompressor.class ) == null && compressionThreshold != -1 ) +- { +- addBefore( PipelineUtils.PACKET_DECODER, "decompress", new PacketDecompressor(compressionThreshold) ); ++ if (ch.pipeline().get(PacketDecompressor.class) == null && compressionThreshold != -1) { ++ PacketDecompressor packetDecompressor = new PacketDecompressor(compressor, compressionThreshold); //BotFilter ++ addBefore(PipelineUtils.PACKET_DECODER, "decompress", packetDecompressor); + } +- if ( compressionThreshold == -1 ) +- { +- pipeline.remove( "decompress" ); ++ if (compressionThreshold == -1) { ++ ch.pipeline().remove("decompress"); + } + } + ++ + // FlameCord start - Antibot System + // Make the channel accessible + public Channel getChannel() { +-- +2.38.1.windows.1 +