package argentoz.protect;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.security.CodeSource;
import java.security.ProtectionDomain;
import java.security.cert.Certificate;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.Arrays;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.concurrent.ConcurrentHashMap;
public class SecureClassLoader extends ClassLoader {
private static final byte[] KEY = {0x48, 0x2A, 0x7C, 0x3F, 0x12, 0x59, 0x6E, (byte) 0x8B, 0x4D, 0x77, 0x0F, 0x3C, (byte) 0x9A, (byte) 0xE3, 0x5D, 0x2B};
private static final byte SIMPLE_KEY = 0x48;
private static final byte[] MASTER_KEY = {
0x48, 0x2A, 0x7C, 0x3F, 0x12, 0x59, 0x6E, (byte)0x8B,
0x4D, 0x77, 0x0F, 0x3C, (byte)0x9A, (byte)0xE3, 0x5D, 0x2B,
0x7F, 0x1D, 0x39, 0x62, (byte)0xA3, 0x27, 0x45, 0x53,
0x69, 0x33, 0x18, (byte)0xD4, (byte)0xE7, 0x0B, 0x74, (byte)0xC9
};
private static final int[] ROTATION_TABLE = {
7, 12, 17, 22, 5, 9, 14, 20, 4, 11, 16, 23, 6, 10, 15, 21
};
private static final byte[] SUBSTITUTION_BOX = {
(byte)0xE2, 0x4F, (byte)0xA1, 0x35, (byte)0xB7, 0x6C, 0x1D, 0x29,
0x58, (byte)0xF3, 0x07, (byte)0xD9, 0x4A, (byte)0x8E, 0x63, (byte)0xC5,
0x3B, 0x72, (byte)0x91, 0x0F, (byte)0xE8, 0x55, (byte)0xCD, 0x36,
(byte)0xA4, 0x2E, (byte)0xF7, 0x1B, 0x60, (byte)0x8C, 0x47, (byte)0xD2
};
private static final byte[] INVERSE_SUBSTITUTION_BOX = createInverseSBox(SUBSTITUTION_BOX);
private final Map<String, byte[]> classData = new HashMap<>();
private final Map<String, Class<?>> loadedClasses = new HashMap<>();
private final Map<String, Integer> decryptionMethod = new ConcurrentHashMap<>();
private final Set<String> problemClasses = new HashSet<>();
private final String jarPath;
private final URL codeBase;
private static final String[] INITIAL_PROBLEM_CLASSES = {
"mpp/venusfr/ui/mainmenu/MainScreen",
"mpp/venusfr/scripts/lua/classes/EntityClass",
"mpp/venusfr/utils/client/IMinecraft",
"mpp/venusfr/events/TickEvent"
};
private static final Set<String> PARENT_LOADED_PACKAGES = new HashSet<>(Arrays.asList(
"mpp.venusfr.scripts.lua.classes.",
"mpp.venusfr.utils.client.",
"mpp.venusfr.events.",
"net.minecraft.",
"net.optifine."
));
private static final int NO_DECRYPT = 0;
private static final int STANDARD_XOR = 1;
private static final int SIMPLE_XOR = 2;
private static final int ADVANCED_ENCRYPTION = 3;
public SecureClassLoader(ClassLoader parent, String jarPath) {
super(parent);
this.jarPath = jarPath;
this.codeBase = getCodeBaseURL();
try {
for (String problemClass : INITIAL_PROBLEM_CLASSES) {
problemClasses.add(problemClass);
}
loadClassesFromJar();
for (String problemClass : problemClasses) {
decryptionMethod.put(problemClass, ADVANCED_ENCRYPTION);
}
System.out.println("SecureClassLoader initialized with " + classData.size() + " classes");
} catch (IOException e) {
throw new RuntimeException("Failed to initialize secure class loader", e);
}
}
private URL getCodeBaseURL() {
try {
return new URL("file:" + jarPath);
} catch (Exception e) {
return null;
}
}
private void loadClassesFromJar() throws IOException {
try (JarFile jarFile = new JarFile(jarPath)) {
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
if (entry.getName().endsWith(".class") &&
!entry.getName().startsWith("argentoz/protect")) {
String className = entry.getName().replace('/', '.').replace(".class", "");
byte[] classBytes = readEntryData(jarFile, entry);
classData.put(className, classBytes);
if (isValidClassFile(classBytes)) {
decryptionMethod.put(entry.getName().replace(".class", ""), ADVANCED_ENCRYPTION);
}
}
}
}
}
private byte[] readEntryData(JarFile jarFile, JarEntry entry) throws IOException {
try (InputStream is = jarFile.getInputStream(entry)) {
byte[] buffer = new byte[(int) entry.getSize()];
int bytesRead = 0;
int totalBytesRead = 0;
while (totalBytesRead < buffer.length) {
bytesRead = is.read(buffer, totalBytesRead, buffer.length - totalBytesRead);
if (bytesRead == -1) break;
totalBytesRead += bytesRead;
}
return buffer;
}
}
@Override
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
Class<?> loadedClass = loadedClasses.get(name);
if (loadedClass != null) {
return loadedClass;
}
try {
if (name.startsWith("argentoz.protect")) {
return super.loadClass(name, resolve);
}
String classPath = name.replace('.', '/');
if (problemClasses.contains(classPath)) {
return super.loadClass(name, resolve);
}
for (String prefix : PARENT_LOADED_PACKAGES) {
if (name.startsWith(prefix)) {
return super.loadClass(name, resolve);
}
}
if (classData.containsKey(name)) {
try {
return findClass(name);
} catch (Exception e) {
problemClasses.add(classPath);
return super.loadClass(name, resolve);
}
}
return super.loadClass(name, resolve);
} catch (Exception e) {
try {
return super.loadClass(name, resolve);
} catch (ClassNotFoundException cnfe) {
throw cnfe;
}
}
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
if (loadedClasses.containsKey(name)) {
return loadedClasses.get(name);
}
byte[] rawClassData = classData.get(name);
if (rawClassData != null) {
String classPath = name.replace('.', '/');
byte[] finalClassData = new byte[0];
Integer method = decryptionMethod.get(classPath);
if (method == null) {
method = detectDecryptionMethod(rawClassData);
decryptionMethod.put(classPath, method);
}
switch (method) {
case NO_DECRYPT:
finalClassData = rawClassData;
break;
case STANDARD_XOR:
finalClassData = standardDecrypt(rawClassData);
break;
case SIMPLE_XOR:
finalClassData = simpleDecrypt(rawClassData);
break;
case ADVANCED_ENCRYPTION:
finalClassData = customDecrypt(rawClassData);
break;
default:
finalClassData = tryMultipleDecryptions(rawClassData, classPath);
}
if (!isValidClassFile(finalClassData)) {
finalClassData = tryMultipleDecryptions(rawClassData, classPath);
if (!isValidClassFile(finalClassData)) {
problemClasses.add(classPath);
return super.findClass(name);
}
}
try {
ProtectionDomain domain = new ProtectionDomain(
new CodeSource(codeBase, (Certificate[]) null),
null, this, null);
Class<?> clazz = defineClass(name, finalClassData, 0, finalClassData.length, domain);
loadedClasses.put(name, clazz);
return clazz;
} catch (Exception e) {
problemClasses.add(classPath);
return super.findClass(name);
}
}
return super.findClass(name);
}
private int detectDecryptionMethod(byte[] data) {
return ADVANCED_ENCRYPTION;
}
private boolean isValidClassFile(byte[] data) {
if (data.length < 8) return false;
boolean validMagic = data[0] == (byte)0xCA &&
data[1] == (byte)0xFE &&
data[2] == (byte)0xBA &&
data[3] == (byte)0xBE;
if (!validMagic) return false;
int major = ((data[6] & 0xFF) << 8) | (data[7] & 0xFF);
return major > 0 && major <= 65;
}
private byte[] standardDecrypt(byte[] encryptedData) {
return encryptedData;
}
private byte[] simpleDecrypt(byte[] encryptedData) {
return encryptedData;
}
private byte[] customDecrypt(byte[] encryptedData) {
byte[] uniqueKey = generateUniqueKey(encryptedData);
byte[] decryptedData = new byte[encryptedData.length];
for (int i = encryptedData.length - 1; i >= 0; i--) {
byte currentByte = encryptedData[i];
int rotation = ROTATION_TABLE[i % ROTATION_TABLE.length];
currentByte = rotateRight(currentByte, rotation);
currentByte = INVERSE_SUBSTITUTION_BOX[currentByte & 0xFF];
currentByte ^= uniqueKey[i % uniqueKey.length];
decryptedData[i] = currentByte;
}
return decryptedData;
}
private byte[] decryptClassData(byte[] encryptedData) {
return decryptClassData(encryptedData);
}
private byte[] tryMultipleDecryptions(byte[] data, String classPath) {
return data;
}
@Override
public URL getResource(String name) {
if (classData.containsKey(name.replace('/', '.').replace(".class", ""))) {
try {
return new URL("jar:file:" + jarPath + "!/" + name);
} catch (Exception e) {
return null;
}
}
return super.getResource(name);
}
@Override
public InputStream getResourceAsStream(String name) {
String className = name.replace('/', '.').replace(".class", "");
if (classData.containsKey(className)) {
String classPath = className.replace('.', '/');
if (problemClasses.contains(classPath)) {
return super.getResourceAsStream(name);
}
byte[] rawData = classData.get(className);
Integer method = decryptionMethod.get(classPath);
byte[] finalData;
if (method != null) {
switch (method) {
case NO_DECRYPT:
finalData = rawData;
break;
case STANDARD_XOR:
finalData = standardDecrypt(rawData);
break;
case SIMPLE_XOR:
finalData = simpleDecrypt(rawData);
break;
case ADVANCED_ENCRYPTION:
finalData = customDecrypt(rawData);
break;
default:
finalData = rawData;
}
} else {
if (isValidClassFile(rawData)) {
finalData = rawData;
decryptionMethod.put(classPath, NO_DECRYPT);
} else {
finalData = tryMultipleDecryptions(rawData, classPath);
}
}
if (isValidClassFile(finalData)) {
return new ByteArrayInputStream(finalData);
} else {
return super.getResourceAsStream(name);
}
}
return super.getResourceAsStream(name);
}
private byte[] generateUniqueKey(byte[] classData) {
byte[] expandedKey = new byte[Math.max(256, classData.length / 4)];
for (int i = 0; i < MASTER_KEY.length && i < expandedKey.length; i++) {
expandedKey[i] = MASTER_KEY[i];
}
int classSize = classData.length;
for (int i = 0; i < expandedKey.length; i++) {
expandedKey[i] ^= (byte)(classSize & 0xFF);
classSize = (classSize >> 8) | (classSize << 24);
int samplePos = (i * 17) % classData.length;
expandedKey[i] ^= classData[samplePos];
if (i > 0) expandedKey[i] ^= expandedKey[i-1];
if (i > 3) expandedKey[i] ^= expandedKey[i-3];
expandedKey[i] = (byte)((expandedKey[i] * 13) + 7);
}
return expandedKey;
}
private byte rotateRight(byte b, int shift) {
return (byte) ((b & 0xFF) >>> shift | (b & 0xFF) << (8 - shift));
}
private static byte[] createInverseSBox(byte[] sBox) {
byte[] inverseSBox = new byte[256];
for (int i = 0; i < sBox.length; i++) {
inverseSBox[sBox[i] & 0xFF] = (byte) i;
}
return inverseSBox;
}
}