diff --git a/Common/Source/pom.xml b/Common/Source/pom.xml
index 4638a47..1a60f07 100644
--- a/Common/Source/pom.xml
+++ b/Common/Source/pom.xml
@@ -47,7 +47,7 @@
org.projectlombok
lombok
- 1.18.30
+ 1.18.44
@@ -196,4 +196,4 @@
-
\ No newline at end of file
+
diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/AntiVPN.java b/Common/Source/src/main/java/dev/brighten/antivpn/AntiVPN.java
index 88b5347..569076c 100644
--- a/Common/Source/src/main/java/dev/brighten/antivpn/AntiVPN.java
+++ b/Common/Source/src/main/java/dev/brighten/antivpn/AntiVPN.java
@@ -272,4 +272,4 @@ public class AntiVPN {
private void registerCommands() {
commands.add(new AntiVPNCommand());
}
-}
\ No newline at end of file
+}
diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/database/local/H2VPN.java b/Common/Source/src/main/java/dev/brighten/antivpn/database/local/H2VPN.java
index 2969feb..a9dae1b 100644
--- a/Common/Source/src/main/java/dev/brighten/antivpn/database/local/H2VPN.java
+++ b/Common/Source/src/main/java/dev/brighten/antivpn/database/local/H2VPN.java
@@ -255,7 +255,6 @@ public class H2VPN implements VPNDatabase {
try {
String cidrString = set.getString("cidr_string");
- AntiVPN.getInstance().getExecutor().log("CIDR String: %s", cidrString);
ips.add(new CIDRUtils(cidrString));
} catch (UnknownHostException e) {
diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/database/sql/utils/MySQL.java b/Common/Source/src/main/java/dev/brighten/antivpn/database/sql/utils/MySQL.java
index 21a433b..c88bfe2 100644
--- a/Common/Source/src/main/java/dev/brighten/antivpn/database/sql/utils/MySQL.java
+++ b/Common/Source/src/main/java/dev/brighten/antivpn/database/sql/utils/MySQL.java
@@ -25,7 +25,6 @@ import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.sql.Connection;
-import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
import java.util.logging.Level;
@@ -36,12 +35,17 @@ public class MySQL {
public static void init() {
try {
if (conn == null || conn.isClosed()) {
- DriverManager.registerDriver(new Driver());
- conn = DriverManager.getConnection("jdbc:mysql://" + AntiVPN.getInstance().getVpnConfig().getIp()
- + ":" + AntiVPN.getInstance().getVpnConfig().getPort()
- + "/?useSSL=true&autoReconnect=true",
- AntiVPN.getInstance().getVpnConfig().getUsername(),
- AntiVPN.getInstance().getVpnConfig().getPassword());
+ String url = "jdbc:mysql://" + AntiVPN.getInstance().getVpnConfig().getIp()
+ + ":" + AntiVPN.getInstance().getVpnConfig().getPort()
+ + "/?useSSL=true&autoReconnect=true";
+ Properties properties = new Properties();
+ properties.setProperty("user", AntiVPN.getInstance().getVpnConfig().getUsername());
+ properties.setProperty("password", AntiVPN.getInstance().getVpnConfig().getPassword());
+
+ conn = new Driver().connect(url, properties);
+ if (conn == null) {
+ throw new SQLException("MySQL driver did not accept URL: " + url);
+ }
conn.setAutoCommit(true);
Query.use(conn);
Query.prepare("CREATE DATABASE IF NOT EXISTS `"
@@ -51,6 +55,7 @@ public class MySQL {
}
} catch (Exception e) {
AntiVPN.getInstance().getExecutor().logException("Failed to load mysql: " + e.getMessage(), e);
+ throw new RuntimeException("Could not initialize MySQL connection", e);
}
}
diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/depends/LibraryLoader.java b/Common/Source/src/main/java/dev/brighten/antivpn/depends/LibraryLoader.java
index 913aada..7a76d45 100644
--- a/Common/Source/src/main/java/dev/brighten/antivpn/depends/LibraryLoader.java
+++ b/Common/Source/src/main/java/dev/brighten/antivpn/depends/LibraryLoader.java
@@ -21,9 +21,14 @@ import dev.brighten.antivpn.utils.NonnullByDefault;
import dev.brighten.antivpn.utils.Supplier;
import dev.brighten.antivpn.utils.Suppliers;
import lombok.Getter;
+import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.RecordComponentVisitor;
import org.objectweb.asm.commons.ClassRemapper;
import org.objectweb.asm.commons.Remapper;
@@ -31,6 +36,7 @@ import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
+import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.*;
import java.util.jar.JarEntry;
@@ -44,6 +50,8 @@ import java.util.jar.JarOutputStream;
@SuppressWarnings("CallToPrintStackTrace")
@NonnullByDefault
public final class LibraryLoader {
+ private static final int RELOCATION_FORMAT_VERSION = 5;
+ private static final String RELOCATION_METADATA_PATH = "META-INF/antivpn-relocation.properties";
@SuppressWarnings("Guava")
private static final Supplier URL_INJECTOR = AntiVPN.getInstance().getClass().getClassLoader() instanceof URLClassLoader ?
@@ -109,8 +117,8 @@ public final class LibraryLoader {
}
}
- // If we have relocations, create a relocated jar
- if (!relocations.isEmpty() && !saveLocation.exists()) {
+ // Rebuild relocated jars when the relocation format changes or the cached jar is stale.
+ if (!relocations.isEmpty() && shouldRebuildRelocatedJar(saveLocation, relocations)) {
try {
System.out.println("Relocating packages for " + name + "...");
relocateJar(originalJar, saveLocation, relocations);
@@ -142,6 +150,8 @@ public final class LibraryLoader {
// Track service files to avoid duplicates
Map serviceFiles = new HashMap<>();
+ Files.deleteIfExists(targetJar.toPath());
+
try (JarFile jar = new JarFile(sourceJar);
JarOutputStream jos = new JarOutputStream(Files.newOutputStream(targetJar.toPath()))) {
@@ -168,12 +178,14 @@ public final class LibraryLoader {
jos.putNextEntry(newEntry);
byte[] classBytes = readAllBytes(is);
- byte[] relocatedBytes = relocateClass(classBytes, relocations);
+ byte[] relocatedBytes = relocateClass(name, classBytes, relocations);
jos.write(relocatedBytes);
jos.closeEntry();
} else {
- // Copy other files as-is
- JarEntry newEntry = new JarEntry(name);
+ // Relocate package-scoped resources so ResourceBundle lookups follow relocated packages.
+ String relocatedPath = relocateResourcePath(name, relocations);
+
+ JarEntry newEntry = new JarEntry(relocatedPath);
jos.putNextEntry(newEntry);
copyStream(is, jos);
jos.closeEntry();
@@ -186,7 +198,7 @@ public final class LibraryLoader {
try {
JarEntry serviceEntry = new JarEntry(entry.getKey());
jos.putNextEntry(serviceEntry);
- jos.write(entry.getValue().toString().getBytes());
+ jos.write(entry.getValue().toString().getBytes(StandardCharsets.UTF_8));
jos.closeEntry();
} catch (Exception e) {
// Log but continue with other service files
@@ -194,7 +206,64 @@ public final class LibraryLoader {
entry.getKey() + ": " + e.getMessage());
}
}
+
+ writeRelocationMetadata(jos, relocations);
}
+
+ validateRelocatedJar(targetJar, relocations);
+ }
+
+ private static boolean shouldRebuildRelocatedJar(File relocatedJar, Map relocations) {
+ if (!relocatedJar.exists()) {
+ return true;
+ }
+
+ try (JarFile jar = new JarFile(relocatedJar)) {
+ JarEntry metadataEntry = jar.getJarEntry(RELOCATION_METADATA_PATH);
+ if (metadataEntry == null) {
+ return true;
+ }
+
+ Properties metadata = new Properties();
+ try (InputStream is = jar.getInputStream(metadataEntry)) {
+ metadata.load(is);
+ }
+
+ if (!String.valueOf(RELOCATION_FORMAT_VERSION).equals(metadata.getProperty("formatVersion"))) {
+ return true;
+ }
+
+ for (Map.Entry relocation : relocations.entrySet()) {
+ String key = "relocation." + relocation.getKey();
+ if (!relocation.getValue().equals(metadata.getProperty(key))) {
+ return true;
+ }
+ }
+
+ return Integer.toString(relocations.size()).equals(metadata.getProperty("relocationCount"));
+ } catch (IOException e) {
+ return true;
+ }
+ }
+
+ private static void writeRelocationMetadata(JarOutputStream jos, Map relocations)
+ throws IOException {
+ Properties metadata = new Properties();
+ metadata.setProperty("formatVersion", Integer.toString(RELOCATION_FORMAT_VERSION));
+ metadata.setProperty("relocationCount", Integer.toString(relocations.size()));
+
+ Map sortedRelocations = new TreeMap<>(relocations);
+ for (Map.Entry relocation : sortedRelocations.entrySet()) {
+ metadata.setProperty("relocation." + relocation.getKey(), relocation.getValue());
+ }
+
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ metadata.store(buffer, "AntiVPN relocation metadata");
+
+ JarEntry metadataEntry = new JarEntry(RELOCATION_METADATA_PATH);
+ jos.putNextEntry(metadataEntry);
+ jos.write(buffer.toByteArray());
+ jos.closeEntry();
}
private static void processServiceFile(String name, InputStream is,
@@ -220,7 +289,7 @@ public final class LibraryLoader {
}
}
- private static byte[] relocateClass(byte[] classBytes, Map relocations) {
+ private static byte[] relocateClass(String entryName, byte[] classBytes, Map relocations) {
try {
// Convert to slash notation for ASM
Remapper prefixRemapper = getPrefixRemapper(relocations);
@@ -239,21 +308,87 @@ public final class LibraryLoader {
}
};
- ClassVisitor visitor = new ClassRemapper(writer, prefixRemapper);
+ ClassVisitor visitor = createStringRelocationVisitor(new ClassRemapper(writer, prefixRemapper), relocations);
+ visitor = createMySqlUtilFallbackVisitor(entryName, visitor);
// Process class with remapper
- reader.accept(visitor, ClassReader.EXPAND_FRAMES);
+ reader.accept(visitor, 0);
- return writer.toByteArray();
+ return relocateUtf8Constants(writer.toByteArray(), relocations);
} catch (Exception e) {
- e.printStackTrace();
- return classBytes;
+ throw new IllegalStateException("Failed to relocate class entry " + entryName, e);
}
}
+ public static String relocateReflectiveClassName(String className) {
+ if (className == null || className.startsWith("dev.brighten.antivpn.shaded.")) {
+ return className;
+ }
+
+ if (className.startsWith("com.mysql.cj") || className.startsWith("com.mysql.jdbc")) {
+ return "dev.brighten.antivpn.shaded." + className;
+ }
+
+ return className;
+ }
+
+ private static byte[] relocateUtf8Constants(byte[] classBytes, Map relocations) throws IOException {
+ Map dotMappings = new HashMap<>();
+ Map slashMappings = new HashMap<>();
+ for (Map.Entry entry : relocations.entrySet()) {
+ dotMappings.put(entry.getKey(), entry.getValue());
+ slashMappings.put(entry.getKey().replace('.', '/'), entry.getValue().replace('.', '/'));
+ }
+
+ DataInputStream in = new DataInputStream(new ByteArrayInputStream(classBytes));
+ ByteArrayOutputStream baos = new ByteArrayOutputStream(classBytes.length + 256);
+ DataOutputStream out = new DataOutputStream(baos);
+
+ out.writeInt(in.readInt());
+ out.writeShort(in.readUnsignedShort());
+ out.writeShort(in.readUnsignedShort());
+
+ int constantPoolCount = in.readUnsignedShort();
+ out.writeShort(constantPoolCount);
+
+ for (int i = 1; i < constantPoolCount; i++) {
+ int tag = in.readUnsignedByte();
+ out.writeByte(tag);
+
+ switch (tag) {
+ case 1 -> {
+ String value = in.readUTF();
+ String relocated = relocateStringValue(value, dotMappings, slashMappings);
+ out.writeUTF(relocated);
+ }
+ case 3, 4 -> out.writeInt(in.readInt());
+ case 5, 6 -> {
+ out.writeLong(in.readLong());
+ i++;
+ }
+ case 7, 8, 16, 19, 20 -> out.writeShort(in.readUnsignedShort());
+ case 9, 10, 11, 12, 17, 18 -> {
+ out.writeShort(in.readUnsignedShort());
+ out.writeShort(in.readUnsignedShort());
+ }
+ case 15 -> {
+ out.writeByte(in.readUnsignedByte());
+ out.writeShort(in.readUnsignedShort());
+ }
+ default -> throw new IOException("Unknown constant pool tag " + tag);
+ }
+ }
+
+ copyStream(in, out);
+ out.flush();
+ return baos.toByteArray();
+ }
+
private static Remapper getPrefixRemapper(Map relocations) {
Map slashMappings = new HashMap<>();
+ Map dotMappings = new HashMap<>();
for (Map.Entry entry : relocations.entrySet()) {
+ dotMappings.put(entry.getKey(), entry.getValue());
String fromSlash = entry.getKey().replace('.', '/');
String toSlash = entry.getValue().replace('.', '/');
slashMappings.put(fromSlash, toSlash);
@@ -275,9 +410,359 @@ public final class LibraryLoader {
}
return typeName;
}
+
+ @Override
+ public Object mapValue(Object value) {
+ if (value instanceof String stringValue) {
+ return relocateStringValue(stringValue, dotMappings, slashMappings);
+ }
+ return super.mapValue(value);
+ }
};
}
+ private static ClassVisitor createMySqlUtilFallbackVisitor(String entryName, ClassVisitor delegate) {
+ if (!"com/mysql/cj/util/Util.class".equals(entryName)) {
+ return delegate;
+ }
+
+ return new ClassVisitor(Opcodes.ASM9, delegate) {
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String descriptor, String signature,
+ String[] exceptions) {
+ MethodVisitor visitor = super.visitMethod(access, name, descriptor, signature, exceptions);
+ if (visitor == null) {
+ return null;
+ }
+
+ if (!"getInstance".equals(name)
+ || !"(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/Class;[Ljava/lang/Object;Lcom/mysql/cj/exceptions/ExceptionInterceptor;)Ljava/lang/Object;".equals(descriptor)) {
+ return visitor;
+ }
+
+ return new MethodVisitor(Opcodes.ASM9, visitor) {
+ @Override
+ public void visitCode() {
+ super.visitCode();
+ super.visitVarInsn(Opcodes.ALOAD, 1);
+ super.visitMethodInsn(Opcodes.INVOKESTATIC,
+ "dev/brighten/antivpn/depends/LibraryLoader",
+ "relocateReflectiveClassName",
+ "(Ljava/lang/String;)Ljava/lang/String;",
+ false);
+ super.visitVarInsn(Opcodes.ASTORE, 1);
+ }
+ };
+ }
+ };
+ }
+
+ private static ClassVisitor createStringRelocationVisitor(ClassVisitor delegate,
+ Map relocations) {
+ Map dotMappings = new HashMap<>();
+ Map slashMappings = new HashMap<>();
+ for (Map.Entry entry : relocations.entrySet()) {
+ dotMappings.put(entry.getKey(), entry.getValue());
+ slashMappings.put(entry.getKey().replace('.', '/'), entry.getValue().replace('.', '/'));
+ }
+
+ return new ClassVisitor(Opcodes.ASM9, delegate) {
+ @Override
+ public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
+ return wrapAnnotationVisitor(super.visitAnnotation(descriptor, visible), dotMappings, slashMappings);
+ }
+
+ @Override
+ public AnnotationVisitor visitTypeAnnotation(int typeRef, org.objectweb.asm.TypePath typePath,
+ String descriptor, boolean visible) {
+ return wrapAnnotationVisitor(super.visitTypeAnnotation(typeRef, typePath, descriptor, visible),
+ dotMappings, slashMappings);
+ }
+
+ @Override
+ public RecordComponentVisitor visitRecordComponent(String name, String descriptor, String signature) {
+ RecordComponentVisitor visitor = super.visitRecordComponent(name, descriptor, signature);
+ if (visitor == null) {
+ return null;
+ }
+ return new RecordComponentVisitor(Opcodes.ASM9, visitor) {
+ @Override
+ public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
+ return wrapAnnotationVisitor(super.visitAnnotation(descriptor, visible),
+ dotMappings, slashMappings);
+ }
+
+ @Override
+ public AnnotationVisitor visitTypeAnnotation(int typeRef, org.objectweb.asm.TypePath typePath,
+ String descriptor, boolean visible) {
+ return wrapAnnotationVisitor(super.visitTypeAnnotation(typeRef, typePath, descriptor, visible),
+ dotMappings, slashMappings);
+ }
+ };
+ }
+
+ @Override
+ public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
+ FieldVisitor visitor = super.visitField(access, name, descriptor, signature,
+ relocateAsmValue(value, dotMappings, slashMappings));
+ if (visitor == null) {
+ return null;
+ }
+ return new FieldVisitor(Opcodes.ASM9, visitor) {
+ @Override
+ public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
+ return wrapAnnotationVisitor(super.visitAnnotation(descriptor, visible),
+ dotMappings, slashMappings);
+ }
+
+ @Override
+ public AnnotationVisitor visitTypeAnnotation(int typeRef, org.objectweb.asm.TypePath typePath,
+ String descriptor, boolean visible) {
+ return wrapAnnotationVisitor(super.visitTypeAnnotation(typeRef, typePath, descriptor, visible),
+ dotMappings, slashMappings);
+ }
+ };
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String descriptor, String signature,
+ String[] exceptions) {
+ MethodVisitor visitor = super.visitMethod(access, name, descriptor, signature, exceptions);
+ if (visitor == null) {
+ return null;
+ }
+ return new MethodVisitor(Opcodes.ASM9, visitor) {
+ @Override
+ public AnnotationVisitor visitAnnotationDefault() {
+ return wrapAnnotationVisitor(super.visitAnnotationDefault(), dotMappings, slashMappings);
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
+ return wrapAnnotationVisitor(super.visitAnnotation(descriptor, visible),
+ dotMappings, slashMappings);
+ }
+
+ @Override
+ public AnnotationVisitor visitTypeAnnotation(int typeRef, org.objectweb.asm.TypePath typePath,
+ String descriptor, boolean visible) {
+ return wrapAnnotationVisitor(super.visitTypeAnnotation(typeRef, typePath, descriptor, visible),
+ dotMappings, slashMappings);
+ }
+
+ @Override
+ public AnnotationVisitor visitParameterAnnotation(int parameter, String descriptor,
+ boolean visible) {
+ return wrapAnnotationVisitor(super.visitParameterAnnotation(parameter, descriptor, visible),
+ dotMappings, slashMappings);
+ }
+
+ @Override
+ public AnnotationVisitor visitInsnAnnotation(int typeRef, org.objectweb.asm.TypePath typePath,
+ String descriptor, boolean visible) {
+ return wrapAnnotationVisitor(super.visitInsnAnnotation(typeRef, typePath, descriptor, visible),
+ dotMappings, slashMappings);
+ }
+
+ @Override
+ public AnnotationVisitor visitTryCatchAnnotation(int typeRef, org.objectweb.asm.TypePath typePath,
+ String descriptor, boolean visible) {
+ return wrapAnnotationVisitor(super.visitTryCatchAnnotation(typeRef, typePath, descriptor, visible),
+ dotMappings, slashMappings);
+ }
+
+ @Override
+ public AnnotationVisitor visitLocalVariableAnnotation(int typeRef,
+ org.objectweb.asm.TypePath typePath,
+ org.objectweb.asm.Label[] start,
+ org.objectweb.asm.Label[] end,
+ int[] index, String descriptor,
+ boolean visible) {
+ return wrapAnnotationVisitor(
+ super.visitLocalVariableAnnotation(typeRef, typePath, start, end, index, descriptor, visible),
+ dotMappings, slashMappings);
+ }
+
+ @Override
+ public void visitLdcInsn(Object value) {
+ super.visitLdcInsn(relocateAsmValue(value, dotMappings, slashMappings));
+ }
+
+ @Override
+ public void visitInvokeDynamicInsn(String name, String descriptor, org.objectweb.asm.Handle bootstrapMethodHandle,
+ Object... bootstrapMethodArguments) {
+ Object[] relocatedArgs = new Object[bootstrapMethodArguments.length];
+ for (int i = 0; i < bootstrapMethodArguments.length; i++) {
+ relocatedArgs[i] = relocateAsmValue(bootstrapMethodArguments[i], dotMappings, slashMappings);
+ }
+ super.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, relocatedArgs);
+ }
+ };
+ }
+ };
+ }
+
+ private static AnnotationVisitor wrapAnnotationVisitor(AnnotationVisitor delegate,
+ Map dotMappings,
+ Map slashMappings) {
+ if (delegate == null) {
+ return null;
+ }
+
+ return new AnnotationVisitor(Opcodes.ASM9, delegate) {
+ @Override
+ public void visit(String name, Object value) {
+ super.visit(name, relocateAsmValue(value, dotMappings, slashMappings));
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String name, String descriptor) {
+ return wrapAnnotationVisitor(super.visitAnnotation(name, descriptor), dotMappings, slashMappings);
+ }
+
+ @Override
+ public AnnotationVisitor visitArray(String name) {
+ return wrapAnnotationVisitor(super.visitArray(name), dotMappings, slashMappings);
+ }
+ };
+ }
+
+ private static Object relocateAsmValue(Object value, Map dotMappings,
+ Map slashMappings) {
+ if (value instanceof String stringValue) {
+ return relocateStringValue(stringValue, dotMappings, slashMappings);
+ }
+
+ return value;
+ }
+
+ private static String relocateStringValue(String value, Map dotMappings,
+ Map slashMappings) {
+ for (Map.Entry entry : dotMappings.entrySet()) {
+ String from = entry.getKey();
+ String relocated = relocateByPrefixes(value, from, entry.getValue(), '.', '$');
+ if (!relocated.equals(value)) {
+ return relocated;
+ }
+ }
+
+ for (Map.Entry entry : slashMappings.entrySet()) {
+ String from = entry.getKey();
+ String to = entry.getValue();
+
+ String relocated = relocateByPrefixes(value, from, to, '/', '$');
+ if (!relocated.equals(value)) {
+ return relocated;
+ }
+
+ relocated = relocateByPrefixes(value, "/" + from, "/" + to, '/', '$');
+ if (!relocated.equals(value)) {
+ return relocated;
+ }
+
+ relocated = relocateByPrefixes(value, "L" + from, "L" + to, '/', '$', ';');
+ if (!relocated.equals(value)) {
+ return relocated;
+ }
+
+ relocated = relocateByPrefixes(value, "[L" + from, "[L" + to, '/', '$', ';');
+ if (!relocated.equals(value)) {
+ return relocated;
+ }
+ }
+
+ return value;
+ }
+
+ private static String relocateByPrefixes(String value, String from, String to, char... delimiters) {
+ if (value.equals(from)) {
+ return to;
+ }
+
+ for (char delimiter : delimiters) {
+ if (value.startsWith(from + delimiter)) {
+ return to + value.substring(from.length());
+ }
+ }
+
+ return value;
+ }
+
+ private static void validateRelocatedJar(File targetJar, Map relocations) throws IOException {
+ Set relocatedPrefixes = new HashSet<>();
+ Map dotMappings = new HashMap<>();
+ Map slashMappings = new HashMap<>();
+ for (Map.Entry relocation : relocations.entrySet()) {
+ relocatedPrefixes.add(relocation.getValue().replace('.', '/') + "/");
+ dotMappings.put(relocation.getKey(), relocation.getValue());
+ slashMappings.put(relocation.getKey().replace('.', '/'), relocation.getValue().replace('.', '/'));
+ }
+
+ try (JarFile jar = new JarFile(targetJar)) {
+ Enumeration entries = jar.entries();
+ while (entries.hasMoreElements()) {
+ JarEntry entry = entries.nextElement();
+ if (entry.isDirectory() || !entry.getName().endsWith(".class")) {
+ continue;
+ }
+
+ boolean shouldValidate = false;
+ for (String relocatedPrefix : relocatedPrefixes) {
+ if (entry.getName().startsWith(relocatedPrefix)) {
+ shouldValidate = true;
+ break;
+ }
+ }
+
+ if (!shouldValidate) {
+ continue;
+ }
+
+ try (InputStream is = jar.getInputStream(entry)) {
+ findUnrelocatedConstant(entry.getName(), readAllBytes(is), dotMappings, slashMappings);
+ }
+ }
+ }
+ }
+
+ private static void findUnrelocatedConstant(String entryName, byte[] classBytes, Map dotMappings,
+ Map slashMappings) throws IOException {
+ DataInputStream in = new DataInputStream(new ByteArrayInputStream(classBytes));
+ in.readInt();
+ in.readUnsignedShort();
+ in.readUnsignedShort();
+ int constantPoolCount = in.readUnsignedShort();
+
+ for (int i = 1; i < constantPoolCount; i++) {
+ int tag = in.readUnsignedByte();
+ switch (tag) {
+ case 1 -> {
+ String value = in.readUTF();
+ String relocated = relocateStringValue(value, dotMappings, slashMappings);
+ if (!value.equals(relocated)) {
+ throw new IOException("Relocated jar still contains original reference '" + value
+ + "' in class entry " + entryName);
+ }
+ }
+ case 3, 4 -> in.readInt();
+ case 5, 6 -> {
+ in.readLong();
+ i++;
+ }
+ case 7, 8, 16, 19, 20 -> in.readUnsignedShort();
+ case 9, 10, 11, 12, 17, 18 -> {
+ in.readUnsignedShort();
+ in.readUnsignedShort();
+ }
+ case 15 -> {
+ in.readUnsignedByte();
+ in.readUnsignedShort();
+ }
+ default -> throw new IOException("Unknown constant pool tag " + tag + " while validating " + entryName);
+ }
+ }
+ }
+
private static String relocateClassPath(String path, Map relocations) {
// Convert path to package format (replacing / with .)
String packagePath = path.substring(0, path.length() - 6).replace('/', '.');
@@ -294,6 +779,23 @@ public final class LibraryLoader {
return packagePath.replace('.', '/') + ".class";
}
+ private static String relocateResourcePath(String path, Map relocations) {
+ if (path.startsWith("META-INF/")) {
+ return path;
+ }
+
+ for (Map.Entry relocation : relocations.entrySet()) {
+ String fromPath = relocation.getKey().replace('.', '/');
+ String toPath = relocation.getValue().replace('.', '/');
+
+ if (path.startsWith(fromPath + "/")) {
+ return toPath + path.substring(fromPath.length());
+ }
+ }
+
+ return path;
+ }
+
private static byte[] readAllBytes(InputStream is) throws IOException {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
int bytesRead;
diff --git a/Common/loader-utils/pom.xml b/Common/loader-utils/pom.xml
index 2a97bec..2244848 100644
--- a/Common/loader-utils/pom.xml
+++ b/Common/loader-utils/pom.xml
@@ -47,7 +47,7 @@
org.projectlombok
lombok
- 1.18.30
+ 1.18.44
diff --git a/Sponge/SpongePlugin/pom.xml b/Sponge/SpongePlugin/pom.xml
index 21fecf6..dc976d0 100644
--- a/Sponge/SpongePlugin/pom.xml
+++ b/Sponge/SpongePlugin/pom.xml
@@ -172,4 +172,4 @@
-
\ No newline at end of file
+
diff --git a/Velocity/VelocityPlugin/src/main/java/dev/brighten/antivpn/velocity/VelocityPlugin.java b/Velocity/VelocityPlugin/src/main/java/dev/brighten/antivpn/velocity/VelocityPlugin.java
index 10760a1..5f76d1d 100644
--- a/Velocity/VelocityPlugin/src/main/java/dev/brighten/antivpn/velocity/VelocityPlugin.java
+++ b/Velocity/VelocityPlugin/src/main/java/dev/brighten/antivpn/velocity/VelocityPlugin.java
@@ -31,7 +31,6 @@ import org.bstats.velocity.Metrics;
import javax.annotation.Nullable;
import java.io.File;
-import java.nio.file.Path;
import java.util.Map;
import java.util.logging.Logger;
@@ -41,7 +40,7 @@ public class VelocityPlugin implements LoaderBootstrap {
private final ProxyServer server;
private final Logger logger;
private final Metrics.Factory metricsFactory;
- private final Path configDir;
+ private File dataFolder;
@Nullable
private Metrics metrics;
@@ -54,7 +53,6 @@ public class VelocityPlugin implements LoaderBootstrap {
public VelocityPlugin(Map, Object> objectsMap) {
this.server = (ProxyServer) objectsMap.get(ProxyServer.class);
this.logger = (Logger) objectsMap.get(Logger.class);
- this.configDir = (Path) objectsMap.get(Path.class);
this.metricsFactory = (Metrics.Factory) objectsMap.get(String.class);
this.pluginInstance = objectsMap.get(LoaderBootstrap.class);
}
@@ -74,7 +72,7 @@ public class VelocityPlugin implements LoaderBootstrap {
@Override
public void onLoad(File dataFolder) {
-
+ this.dataFolder = dataFolder;
}
@Override
@@ -84,7 +82,7 @@ public class VelocityPlugin implements LoaderBootstrap {
//Loading plugin
logger.info("Starting AntiVPN services...");
- AntiVPN.start(new VelocityListener(), new VelocityPlayerExecutor(), configDir.toFile());
+ AntiVPN.start(new VelocityListener(), new VelocityPlayerExecutor(), dataFolder);
if(AntiVPN.getInstance().getVpnConfig().metrics()) {
diff --git a/pom.xml b/pom.xml
index 8916900..efd8dd1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -58,7 +58,7 @@
org.projectlombok
lombok
- 1.18.30
+ 1.18.44
@@ -83,7 +83,7 @@
org.projectlombok
lombok
- 1.18.30
+ 1.18.44
provided