From 1969191dcd90316d7c5cc5c319dc12d684bb1086 Mon Sep 17 00:00:00 2001 From: Dogboy21 Date: Sun, 6 Aug 2023 22:12:06 +0200 Subject: [PATCH] Version 1.4 (#61) * core(cfois): support multi-dimensional arrays (#19) * Improve compatibility with older versions and newer Fabric servers (#43) * Abstracted logger to fall back to native if Log4J isn't present * chore: shadow gson into the final jar to support older versions correctly * fix: initialize config check request with TLS 1.2 Java 7 seems to have TLS 1.2 disabled by default --------- Co-authored-by: ThePixelbrain <19214217+ThePixelbrain@users.noreply.github.com> * fix/feat(modlauncher): load deserialized classes from the correct class loader (#45) * feat: also redirect SerializationUtils.deserialize in patched classes (#46) * feat: change NativeLogger to println Although println is definitely not the most fancy logging implementation as a fallback, there were problems with the default Java logger in < MC 1.7. This is the most stable for these versions and also provides advantages for Fabric servers as well * chore: exclude java 9 files from the bundle jar * fix: bundle ASM lib to fix patching issues in some Fabric setups * feat: allow configuration of remote config loading * fix: create config directory if it does not exist * fix: add workaround to classloading issues in Forge < 1.13 when the agent is used * feat: add timeout to remote config reading 60 seconds should be sufficient (I hope) * feat: use gson with pretty printing for the default local config * fix(modlauncher): return additionalClassesLocator even when the agent is loaded This should fix some classloading issues in the Java 8 version of Modlauncher when the agent is used --------- Co-authored-by: ThatGamerBlue Co-authored-by: ThePixelbrain <19214217+ThePixelbrain@users.noreply.github.com> --- .../agent/SIBTransformer.java | 20 +++-- .../agent/SerializationIsBadAgent.java | 23 +++++ build.gradle | 5 +- core/build.gradle | 18 +++- .../core/ClassFilteringObjectInputStream.java | 47 +++++++++-- .../serializationisbad/core/Patches.java | 64 +++++++++++++- .../core/SerializationIsBad.java | 84 +++++++++++++++---- .../core/config/SIBConfig.java | 20 +++++ .../core/logger/ILogger.java | 8 ++ .../core/logger/Log4JLogger.java | 31 +++++++ .../core/logger/NativeLogger.java | 39 +++++++++ core/src/main/resources/LICENSE-asm.txt | 28 +++++++ gradle.properties | 2 +- .../legacyforge/SIBTransformer.java | 8 +- .../modlauncher/SIBTransformer.java | 23 ++++- ...rializationIsBadTransformationService.java | 2 - 16 files changed, 374 insertions(+), 48 deletions(-) create mode 100644 core/src/main/java/io/dogboy/serializationisbad/core/logger/ILogger.java create mode 100644 core/src/main/java/io/dogboy/serializationisbad/core/logger/Log4JLogger.java create mode 100644 core/src/main/java/io/dogboy/serializationisbad/core/logger/NativeLogger.java create mode 100644 core/src/main/resources/LICENSE-asm.txt diff --git a/agent/src/main/java/io/dogboy/serializationisbad/agent/SIBTransformer.java b/agent/src/main/java/io/dogboy/serializationisbad/agent/SIBTransformer.java index fd1e571..a38dd21 100644 --- a/agent/src/main/java/io/dogboy/serializationisbad/agent/SIBTransformer.java +++ b/agent/src/main/java/io/dogboy/serializationisbad/agent/SIBTransformer.java @@ -2,23 +2,25 @@ import io.dogboy.serializationisbad.core.Patches; import io.dogboy.serializationisbad.core.SerializationIsBad; -import org.objectweb.asm.tree.ClassNode; import java.lang.instrument.ClassFileTransformer; -import java.lang.instrument.IllegalClassFormatException; import java.security.ProtectionDomain; public class SIBTransformer implements ClassFileTransformer { @Override - public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { - String classNameDots = className.replace('/', '.'); + public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) { + try { + if (className == null) return classfileBuffer; + if ("net/minecraft/launchwrapper/ITweaker".equals(className)) SerializationIsBadAgent.insertLaunchWrapperExclusion(); - if (Patches.getPatchModuleForClass(classNameDots) == null) return classfileBuffer; + String classNameDots = className.replace('/', '.'); - SerializationIsBad.logger.info("Applying patches to " + classNameDots); + if (Patches.getPatchModuleForClass(classNameDots) == null) return classfileBuffer; - ClassNode classNode = Patches.readClassNode(classfileBuffer); - Patches.applyPatches(classNameDots, classNode); - return Patches.writeClassNode(classNode); + return Patches.patchClass(classfileBuffer, classNameDots, false); + } catch (Throwable e) { + SerializationIsBad.logger.error("Failed to run agent class transformer", e); + return classfileBuffer; + } } } diff --git a/agent/src/main/java/io/dogboy/serializationisbad/agent/SerializationIsBadAgent.java b/agent/src/main/java/io/dogboy/serializationisbad/agent/SerializationIsBadAgent.java index 3d37f54..9198f8b 100644 --- a/agent/src/main/java/io/dogboy/serializationisbad/agent/SerializationIsBadAgent.java +++ b/agent/src/main/java/io/dogboy/serializationisbad/agent/SerializationIsBadAgent.java @@ -4,6 +4,8 @@ import java.io.File; import java.lang.instrument.Instrumentation; +import java.lang.reflect.Field; +import java.lang.reflect.Method; public class SerializationIsBadAgent { @@ -12,4 +14,25 @@ public static void premain(String agentArgs, Instrumentation inst) { inst.addTransformer(new SIBTransformer()); } + /** + * When SIB is loaded as a JVM agent in a LaunchWrapper environment, + * we need to add an exclusion to the LaunchClassLoader so SIB classes + * always get loaded with the parent classloader. + * Otherwise, mod classes create a separate SIB instance which is not initialized. + * + * I know this is an ugly hack, please don't judge me. + */ + static void insertLaunchWrapperExclusion() { + try { + Class launchClass = Class.forName("net.minecraft.launchwrapper.Launch"); + Field classLoaderField = launchClass.getDeclaredField("classLoader"); + Object classLoader = classLoaderField.get(null); + Class classLoaderClass = Class.forName("net.minecraft.launchwrapper.LaunchClassLoader"); + Method addClassLoaderExclusion = classLoaderClass.getDeclaredMethod("addClassLoaderExclusion", String.class); + addClassLoaderExclusion.invoke(classLoader, "io.dogboy.serializationisbad"); + } catch (Exception e) { + SerializationIsBad.logger.error("Failed to add LaunchWrapper exclusion", e); + } + } + } diff --git a/build.gradle b/build.gradle index 5fdf3f5..18ad490 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,6 @@ plugins { id 'java' + id "com.github.johnrengelman.shadow" version "8.1.1" apply false } subprojects { @@ -19,12 +20,14 @@ subprojects { task bundleJar(type: Jar, dependsOn: subprojects.assemble) { archivesBaseName = rootProject.name - from project(':core').configurations.archives.allArtifacts.files.collect { zipTree(it) } + from zipTree(new File (project(':core').buildDir, "libs/core-${project(':core').version}-all.jar")) // i'm currently unable to correctly get the shadowJar output, so this should work for now from project(':legacyforge').configurations.archives.allArtifacts.files.collect { zipTree(it) } from project(':modlauncher').configurations.archives.allArtifacts.files.collect { zipTree(it) } from project(':agent').configurations.archives.allArtifacts.files.collect { zipTree(it) } from 'LICENSE.txt' + exclude 'META-INF/versions/**' + manifest { from subprojects.collect{ it.jar.manifest } } diff --git a/core/build.gradle b/core/build.gradle index b68ad01..154e8e3 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -1,8 +1,9 @@ apply plugin: 'java-library' +apply plugin: 'com.github.johnrengelman.shadow' dependencies { implementation 'com.google.code.gson:gson:2.10.1' - api 'org.apache.logging.log4j:log4j-api:2.20.0' + implementation 'org.apache.logging.log4j:log4j-api:2.20.0' api 'org.ow2.asm:asm-tree:9.5' } @@ -19,3 +20,18 @@ jar { ]) } } + +shadowJar { + dependencies { + exclude(dependency('org.apache.logging.log4j:log4j-api')) + } + + relocate 'com.google.gson', 'io.dogboy.serializationisbad.shadow.gson' + relocate 'org.objectweb.asm', 'io.dogboy.serializationisbad.shadow.asm' +} + +artifacts { + archives shadowJar +} + +build.dependsOn shadowJar diff --git a/core/src/main/java/io/dogboy/serializationisbad/core/ClassFilteringObjectInputStream.java b/core/src/main/java/io/dogboy/serializationisbad/core/ClassFilteringObjectInputStream.java index d86ccbc..6432d82 100644 --- a/core/src/main/java/io/dogboy/serializationisbad/core/ClassFilteringObjectInputStream.java +++ b/core/src/main/java/io/dogboy/serializationisbad/core/ClassFilteringObjectInputStream.java @@ -6,21 +6,31 @@ import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectStreamClass; +import java.util.HashMap; import java.util.HashSet; import java.util.Set; public class ClassFilteringObjectInputStream extends ObjectInputStream { private final PatchModule patchModule; + private final ClassLoader parentClassLoader; - public ClassFilteringObjectInputStream(InputStream in, PatchModule patchModule) throws IOException { + public ClassFilteringObjectInputStream(InputStream in, PatchModule patchModule, ClassLoader parentClassLoader) throws IOException { super(in); this.patchModule = patchModule; + this.parentClassLoader = parentClassLoader; + } + + public ClassFilteringObjectInputStream(InputStream in, PatchModule patchModule) throws IOException { + this(in, patchModule, null); } private boolean isClassAllowed(String className) { - if (className.startsWith("[L") && className.endsWith(";")) { - className = className.substring(2, className.length() - 1); - } else if (className.startsWith("L") && className.endsWith(";")) { + // strip all array dimensions, just get the base type + while (className.startsWith("[")) { + className = className.substring(1); + } + + if (className.startsWith("L") && className.endsWith(";")) { className = className.substring(1, className.length() - 1); } @@ -51,7 +61,34 @@ protected Class resolveClass(ObjectStreamClass desc) throws IOException, Clas throw new ClassNotFoundException("Class " + desc.getName() + " is not allowed to be deserialized"); } - return super.resolveClass(desc); + if (this.parentClassLoader == null) { + return super.resolveClass(desc); + } + + String name = desc.getName(); + try { + return Class.forName(name, false, this.parentClassLoader); + } catch (ClassNotFoundException ex) { + Class cl = ClassFilteringObjectInputStream.primClasses.get(name); + if (cl != null) { + return cl; + } else { + throw ex; + } + } + } + + private static final HashMap> primClasses = new HashMap<>(8, 1.0F); + static { + ClassFilteringObjectInputStream.primClasses.put("boolean", boolean.class); + ClassFilteringObjectInputStream.primClasses.put("byte", byte.class); + ClassFilteringObjectInputStream.primClasses.put("char", char.class); + ClassFilteringObjectInputStream.primClasses.put("short", short.class); + ClassFilteringObjectInputStream.primClasses.put("int", int.class); + ClassFilteringObjectInputStream.primClasses.put("long", long.class); + ClassFilteringObjectInputStream.primClasses.put("float", float.class); + ClassFilteringObjectInputStream.primClasses.put("double", double.class); + ClassFilteringObjectInputStream.primClasses.put("void", void.class); } } diff --git a/core/src/main/java/io/dogboy/serializationisbad/core/Patches.java b/core/src/main/java/io/dogboy/serializationisbad/core/Patches.java index 29490ae..dab88f9 100644 --- a/core/src/main/java/io/dogboy/serializationisbad/core/Patches.java +++ b/core/src/main/java/io/dogboy/serializationisbad/core/Patches.java @@ -11,6 +11,10 @@ import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.MethodNode; import org.objectweb.asm.tree.TypeInsnNode; +import org.objectweb.asm.tree.VarInsnNode; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; public class Patches { @@ -24,20 +28,22 @@ public static PatchModule getPatchModuleForClass(String className) { return null; } - public static ClassNode readClassNode(byte[] classBytecode) { + private static ClassNode readClassNode(byte[] classBytecode) { ClassNode classNode = new ClassNode(); ClassReader classReader = new ClassReader(classBytecode); classReader.accept(classNode, 0); return classNode; } - public static byte[] writeClassNode(ClassNode classNode) { + private static byte[] writeClassNode(ClassNode classNode) { ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); classNode.accept(writer); return writer.toByteArray(); } - public static void applyPatches(String className, ClassNode classNode) { + private static void applyPatches(String className, ClassNode classNode, boolean passClassLoader) { + SerializationIsBad.logger.info("Applying patches to " + className); + for (MethodNode methodNode : classNode.methods) { InsnList instructions = methodNode.instructions; for (int i = 0; i < instructions.size(); i++) { @@ -53,17 +59,69 @@ public static void applyPatches(String className, ClassNode classNode) { ((MethodInsnNode) instruction).owner = "io/dogboy/serializationisbad/core/ClassFilteringObjectInputStream"; ((MethodInsnNode) instruction).desc = "(Ljava/io/InputStream;Lio/dogboy/serializationisbad/core/config/PatchModule;)V"; + if (passClassLoader) { + ((MethodInsnNode) instruction).desc = "(Ljava/io/InputStream;Lio/dogboy/serializationisbad/core/config/PatchModule;Ljava/lang/ClassLoader;)V"; + } + InsnList additionalInstructions = new InsnList(); additionalInstructions.add(new LdcInsnNode(className)); additionalInstructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "io/dogboy/serializationisbad/core/Patches", "getPatchModuleForClass", "(Ljava/lang/String;)Lio/dogboy/serializationisbad/core/config/PatchModule;", false)); + if (passClassLoader) { + additionalInstructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this + additionalInstructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;", false)); + additionalInstructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/lang/Class", "getClassLoader", "()Ljava/lang/ClassLoader;", false)); + } + instructions.insertBefore(instruction, additionalInstructions); SerializationIsBad.logger.info(" (2/2) Redirecting ObjectInputStream to ClassFilteringObjectInputStream in method " + methodNode.name); + } else if (instruction.getOpcode() == Opcodes.INVOKESTATIC + && instruction instanceof MethodInsnNode + && "org/apache/commons/lang3/SerializationUtils".equals(((MethodInsnNode) instruction).owner) + && "deserialize".equals(((MethodInsnNode) instruction).name)) { + + ((MethodInsnNode) instruction).owner = "io/dogboy/serializationisbad/core/Patches"; + + if ("(Ljava/io/InputStream;)Ljava/lang/Object;".equals(((MethodInsnNode) instruction).desc)) { + ((MethodInsnNode) instruction).desc = "(Ljava/io/InputStream;Lio/dogboy/serializationisbad/core/config/PatchModule;)Ljava/lang/Object;"; + } else if ("([B)Ljava/lang/Object;".equals(((MethodInsnNode) instruction).desc)) { + ((MethodInsnNode) instruction).desc = "([BLio/dogboy/serializationisbad/core/config/PatchModule;)Ljava/lang/Object;"; + } else { + throw new RuntimeException("Unknown desc for SerializationUtils.deserialize: " + ((MethodInsnNode) instruction).desc); + } + + InsnList additionalInstructions = new InsnList(); + additionalInstructions.add(new LdcInsnNode(className)); + additionalInstructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "io/dogboy/serializationisbad/core/Patches", + "getPatchModuleForClass", "(Ljava/lang/String;)Lio/dogboy/serializationisbad/core/config/PatchModule;", false)); + + instructions.insertBefore(instruction, additionalInstructions); + + SerializationIsBad.logger.info(" Redirecting SerializationUtils.deserialize to Patches in method " + methodNode.name); } } } } + public static byte[] patchClass(byte[] classBytes, String className, boolean passClassLoader) { + ClassNode classNode = Patches.readClassNode(classBytes); + Patches.applyPatches(className, classNode, passClassLoader); + return Patches.writeClassNode(classNode); + } + + @SuppressWarnings("unchecked") + public static T deserialize(InputStream inputStream, PatchModule patchModule) { + try (ClassFilteringObjectInputStream objectInputStream = new ClassFilteringObjectInputStream(inputStream, patchModule)) { + return (T) objectInputStream.readObject(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static T deserialize(byte[] objectData, PatchModule patchModule) { + return Patches.deserialize(new ByteArrayInputStream(objectData), patchModule); + } + } diff --git a/core/src/main/java/io/dogboy/serializationisbad/core/SerializationIsBad.java b/core/src/main/java/io/dogboy/serializationisbad/core/SerializationIsBad.java index 3e5610d..6bef410 100644 --- a/core/src/main/java/io/dogboy/serializationisbad/core/SerializationIsBad.java +++ b/core/src/main/java/io/dogboy/serializationisbad/core/SerializationIsBad.java @@ -1,23 +1,31 @@ package io.dogboy.serializationisbad.core; import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import io.dogboy.serializationisbad.core.config.SIBConfig; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import io.dogboy.serializationisbad.core.logger.ILogger; +import io.dogboy.serializationisbad.core.logger.Log4JLogger; +import io.dogboy.serializationisbad.core.logger.NativeLogger; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; import java.io.File; import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; import java.io.InputStreamReader; +import java.net.URL; import java.nio.charset.StandardCharsets; +import java.security.SecureRandom; public class SerializationIsBad { - public static final Logger logger = LogManager.getLogger(SerializationIsBad.class); + public static final ILogger logger = SerializationIsBad.initLogger(SerializationIsBad.class.getCanonicalName()); private static SerializationIsBad instance; private static boolean agentActive = false; - private static final String remoteConfigUrl = "https://raw.githubusercontent.com/dogboy21/serializationisbad/master/serializationisbad.json"; - public static SerializationIsBad getInstance() { + if (SerializationIsBad.instance == null) throw new IllegalStateException("SerializationIsBad has not been initialized yet"); + return SerializationIsBad.instance; } @@ -35,10 +43,25 @@ public static void init(File minecraftDir) { SerializationIsBad.instance = new SerializationIsBad(minecraftDir); } + private static ILogger initLogger(String name) { + try { + // Check if needed Log4J classes are available + Class.forName("org.apache.logging.log4j.LogManager"); + Class.forName("org.apache.logging.log4j.Logger"); + return new Log4JLogger(name); + } catch (ClassNotFoundException e) { + // Fallback to Java native logger + return new NativeLogger(name); + } + } + private final SIBConfig config; private SerializationIsBad(File minecraftDir) { this.config = SerializationIsBad.readConfig(minecraftDir); + if (this.config.getPatchModules().isEmpty()) { + throw new RuntimeException("You are currently using SerializationIsBad without any patch modules configured. The mod on its own doesn't do anything so please install a config file patching the vulnerabilities"); + } SerializationIsBad.logger.info("Loaded config file"); SerializationIsBad.logger.info(" Blocking Enabled: " + this.config.isExecuteBlocking()); @@ -51,28 +74,55 @@ public SIBConfig getConfig() { private static SIBConfig readConfig(File minecraftDir) { File configFile = new File(new File(minecraftDir, "config"), "serializationisbad.json"); + Gson gson = new GsonBuilder().setPrettyPrinting().create(); - SIBConfig remoteConfig = SerializationIsBad.readRemoteConfig(); - if (remoteConfig != null) { - SerializationIsBad.logger.info("Using remote config file"); - return remoteConfig; - } else if (configFile.isFile()) { - Gson gson = new Gson(); + SIBConfig localConfig = new SIBConfig(); + if (configFile.isFile()) { try (FileInputStream fileInputStream = new FileInputStream(configFile)) { - return gson.fromJson(new InputStreamReader(fileInputStream, StandardCharsets.UTF_8), + localConfig = gson.fromJson(new InputStreamReader(fileInputStream, StandardCharsets.UTF_8), SIBConfig.class); } catch (Exception e) { - SerializationIsBad.logger.error("Failed to load config file", e); + throw new RuntimeException("Failed to load local config file", e); } + } else { + configFile.getParentFile().mkdirs(); + try (FileOutputStream fileOutputStream = new FileOutputStream(configFile)) { + fileOutputStream.write(gson.toJson(localConfig).getBytes(StandardCharsets.UTF_8)); + } catch (IOException e) { + throw new RuntimeException("Failed to create local config file", e); + } + } + + if (!localConfig.isUseRemoteConfig()) { + SerializationIsBad.logger.info("Using local config file"); + return localConfig; } - throw new RuntimeException("You are currently using SerializationIsBad without a config file. The mod on its own doesn't do anything so please install a config file patching the vulnerabilities to " + configFile); + SIBConfig remoteConfig = SerializationIsBad.readRemoteConfig(localConfig.getRemoteConfigUrl()); + if (remoteConfig != null) { + SerializationIsBad.logger.info("Using remote config file"); + return remoteConfig; + } + + SerializationIsBad.logger.info("Using local config file as a fallback"); + return localConfig; } - private static SIBConfig readRemoteConfig() { + private static SIBConfig readRemoteConfig(String url) { Gson gson = new Gson(); - try (InputStreamReader inputStreamReader = new InputStreamReader(new java.net.URL(SerializationIsBad.remoteConfigUrl).openStream(), StandardCharsets.UTF_8)) { - return gson.fromJson(inputStreamReader, SIBConfig.class); + try { + HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection(); + SSLContext sslContext = SSLContext.getInstance("TLSv1.2"); + sslContext.init(null, null, new SecureRandom()); + connection.setSSLSocketFactory(sslContext.getSocketFactory()); + connection.setConnectTimeout(60000); + connection.setReadTimeout(60000); + + if (connection.getResponseCode() != 200) throw new IOException("Invalid response code: " + connection.getResponseCode()); + + try (InputStreamReader inputStreamReader = new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8)) { + return gson.fromJson(inputStreamReader, SIBConfig.class); + } } catch (Exception e) { SerializationIsBad.logger.error("Failed to load remote config file", e); } diff --git a/core/src/main/java/io/dogboy/serializationisbad/core/config/SIBConfig.java b/core/src/main/java/io/dogboy/serializationisbad/core/config/SIBConfig.java index d29f338..84e4110 100644 --- a/core/src/main/java/io/dogboy/serializationisbad/core/config/SIBConfig.java +++ b/core/src/main/java/io/dogboy/serializationisbad/core/config/SIBConfig.java @@ -6,18 +6,38 @@ import java.util.Set; public class SIBConfig { + private boolean useRemoteConfig; + private String remoteConfigUrl; private boolean executeBlocking; private List patchModules; private Set classAllowlist; private Set packageAllowlist; public SIBConfig() { + this.useRemoteConfig = true; + this.remoteConfigUrl = "https://raw.githubusercontent.com/dogboy21/serializationisbad/master/serializationisbad.json"; this.executeBlocking = true; this.patchModules = new ArrayList<>(); this.classAllowlist = new HashSet<>(); this.packageAllowlist = new HashSet<>(); } + public boolean isUseRemoteConfig() { + return this.useRemoteConfig; + } + + public void setUseRemoteConfig(boolean useRemoteConfig) { + this.useRemoteConfig = useRemoteConfig; + } + + public String getRemoteConfigUrl() { + return this.remoteConfigUrl; + } + + public void setRemoteConfigUrl(String remoteConfigUrl) { + this.remoteConfigUrl = remoteConfigUrl; + } + public boolean isExecuteBlocking() { return this.executeBlocking; } diff --git a/core/src/main/java/io/dogboy/serializationisbad/core/logger/ILogger.java b/core/src/main/java/io/dogboy/serializationisbad/core/logger/ILogger.java new file mode 100644 index 0000000..8ac1e3d --- /dev/null +++ b/core/src/main/java/io/dogboy/serializationisbad/core/logger/ILogger.java @@ -0,0 +1,8 @@ +package io.dogboy.serializationisbad.core.logger; + +public interface ILogger { + void debug(String message); + void info(String message); + void warn(String message); + void error(String message, Throwable throwable); +} diff --git a/core/src/main/java/io/dogboy/serializationisbad/core/logger/Log4JLogger.java b/core/src/main/java/io/dogboy/serializationisbad/core/logger/Log4JLogger.java new file mode 100644 index 0000000..41b3557 --- /dev/null +++ b/core/src/main/java/io/dogboy/serializationisbad/core/logger/Log4JLogger.java @@ -0,0 +1,31 @@ +package io.dogboy.serializationisbad.core.logger; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class Log4JLogger implements ILogger { + private final Logger logger; + + public Log4JLogger(String name) { + this.logger = LogManager.getLogger(name); + } + @Override + public void debug(String message) { + logger.debug(message); + } + + @Override + public void info(String message) { + logger.info(message); + } + + @Override + public void warn(String message) { + logger.warn(message); + } + + @Override + public void error(String message, Throwable throwable) { + logger.error(message, throwable); + } +} diff --git a/core/src/main/java/io/dogboy/serializationisbad/core/logger/NativeLogger.java b/core/src/main/java/io/dogboy/serializationisbad/core/logger/NativeLogger.java new file mode 100644 index 0000000..978b2ef --- /dev/null +++ b/core/src/main/java/io/dogboy/serializationisbad/core/logger/NativeLogger.java @@ -0,0 +1,39 @@ +package io.dogboy.serializationisbad.core.logger; + + +public class NativeLogger implements ILogger { + private static final boolean debugEnabled = System.getProperty("serializationisbad.nativelogger.debug", "false").equalsIgnoreCase("true"); + + private final String name; + + public NativeLogger(String name) { + this.name = name; + } + + private void log(String level, String message) { + System.out.println("[" + level + "] [" + name + "]: " + message); + } + + @Override + public void debug(String message) { + if (NativeLogger.debugEnabled) { + this.log("DEBUG", message); + } + } + + @Override + public void info(String message) { + this.log("INFO", message); + } + + @Override + public void warn(String message) { + this.log("WARN", message); + } + + @Override + public void error(String message, Throwable throwable) { + this.log("ERROR", message); + throwable.printStackTrace(); + } +} diff --git a/core/src/main/resources/LICENSE-asm.txt b/core/src/main/resources/LICENSE-asm.txt new file mode 100644 index 0000000..4d19185 --- /dev/null +++ b/core/src/main/resources/LICENSE-asm.txt @@ -0,0 +1,28 @@ + + ASM: a very small and fast Java bytecode manipulation framework + Copyright (c) 2000-2011 INRIA, France Telecom + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of the copyright holders nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + THE POSSIBILITY OF SUCH DAMAGE. diff --git a/gradle.properties b/gradle.properties index 8f8eddf..1c404c4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ group=io.dogboy.serializationisbad name=serializationisbad -version=1.3 +version=1.4 diff --git a/legacyforge/src/main/java/io/dogboy/serializationisbad/legacyforge/SIBTransformer.java b/legacyforge/src/main/java/io/dogboy/serializationisbad/legacyforge/SIBTransformer.java index b82bc3d..477d453 100644 --- a/legacyforge/src/main/java/io/dogboy/serializationisbad/legacyforge/SIBTransformer.java +++ b/legacyforge/src/main/java/io/dogboy/serializationisbad/legacyforge/SIBTransformer.java @@ -1,19 +1,13 @@ package io.dogboy.serializationisbad.legacyforge; import io.dogboy.serializationisbad.core.Patches; -import io.dogboy.serializationisbad.core.SerializationIsBad; import net.minecraft.launchwrapper.IClassTransformer; -import org.objectweb.asm.tree.ClassNode; public class SIBTransformer implements IClassTransformer { @Override public byte[] transform(String name, String transformedName, byte[] basicClass) { if (Patches.getPatchModuleForClass(transformedName) == null) return basicClass; - SerializationIsBad.logger.info("Applying patches to " + transformedName); - - ClassNode classNode = Patches.readClassNode(basicClass); - Patches.applyPatches(transformedName, classNode); - return Patches.writeClassNode(classNode); + return Patches.patchClass(basicClass, transformedName, false); } } diff --git a/modlauncher/src/main/java/io/dogboy/serializationisbad/modlauncher/SIBTransformer.java b/modlauncher/src/main/java/io/dogboy/serializationisbad/modlauncher/SIBTransformer.java index 14437db..16d41a2 100644 --- a/modlauncher/src/main/java/io/dogboy/serializationisbad/modlauncher/SIBTransformer.java +++ b/modlauncher/src/main/java/io/dogboy/serializationisbad/modlauncher/SIBTransformer.java @@ -5,6 +5,8 @@ import cpw.mods.modlauncher.api.TransformerVoteResult; import io.dogboy.serializationisbad.core.Patches; import io.dogboy.serializationisbad.core.config.PatchModule; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; import org.objectweb.asm.tree.ClassNode; import java.util.Set; @@ -19,8 +21,12 @@ public SIBTransformer(PatchModule patchModule) { @Override public ClassNode transform(ClassNode input, ITransformerVotingContext context) { - Patches.applyPatches(input.name, input); - return input; + // do not look at this weird fucking shit + // we need to write -> patch -> read the classes, so we can use the core shadowed ASM lib properly + + byte[] classBytes = SIBTransformer.writeClassNode(input); + byte[] transformedClassBytes = Patches.patchClass(classBytes, input.name.replace('/', '.'), true); + return SIBTransformer.readClassNode(transformedClassBytes); } @Override @@ -35,4 +41,17 @@ public Set targets() { .collect(Collectors.toSet()); } + private static ClassNode readClassNode(byte[] classBytecode) { + ClassNode classNode = new ClassNode(); + ClassReader classReader = new ClassReader(classBytecode); + classReader.accept(classNode, 0); + return classNode; + } + + private static byte[] writeClassNode(ClassNode classNode) { + ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); + classNode.accept(writer); + return writer.toByteArray(); + } + } diff --git a/modlauncher/src/main/java/io/dogboy/serializationisbad/modlauncher/SerializationIsBadTransformationService.java b/modlauncher/src/main/java/io/dogboy/serializationisbad/modlauncher/SerializationIsBadTransformationService.java index 0bac2f2..fe55c13 100644 --- a/modlauncher/src/main/java/io/dogboy/serializationisbad/modlauncher/SerializationIsBadTransformationService.java +++ b/modlauncher/src/main/java/io/dogboy/serializationisbad/modlauncher/SerializationIsBadTransformationService.java @@ -53,8 +53,6 @@ public void beginScanning(IEnvironment environment) { } @Override public Map.Entry, Supplier>>> additionalClassesLocator() { - if (SerializationIsBad.isAgentActive()) return null; - return new Map.Entry, Supplier>>>() { @Override public Set getKey() {