From 5602dcd5e0ea98ad9366143d12a369db077749b6 Mon Sep 17 00:00:00 2001 From: Fox2Code Date: Mon, 27 Mar 2023 18:06:11 +0200 Subject: [PATCH] Update 0.3.0 --- client/build.gradle | 6 +- common/build.gradle | 63 +- common/generate.gradle | 258 ++++++++ .../fox2code/foxloader/loader/PreLoader.java | 28 +- .../DevelopmentModeTransformer.java | 11 + .../DevelopmentSourceTransformer.java | 615 ++++++++++++++++++ .../loader/transformer/TransformerUtils.java | 40 ++ .../transformer/VarNameTransformer.java | 85 +++ .../DevelopmentSourceTransformerTests.java | 107 +++ dev/build.gradle | 5 + .../foxloader/dev/FoxLoaderDecompiler.java | 89 +++ .../foxloader/dev/GradlePlugin.groovy | 67 +- .../fox2code/foxloader/dev/UserMessage.java | 37 ++ gradle.properties | 4 +- server/build.gradle | 2 +- 15 files changed, 1343 insertions(+), 74 deletions(-) create mode 100644 common/generate.gradle create mode 100644 common/src/main/java/com/fox2code/foxloader/loader/transformer/DevelopmentSourceTransformer.java create mode 100644 common/src/main/java/com/fox2code/foxloader/loader/transformer/VarNameTransformer.java create mode 100644 common/src/test/java/com/fox2code/foxloader/testes/DevelopmentSourceTransformerTests.java create mode 100644 dev/src/main/groovy/com/fox2code/foxloader/dev/FoxLoaderDecompiler.java create mode 100644 dev/src/main/groovy/com/fox2code/foxloader/dev/UserMessage.java diff --git a/client/build.gradle b/client/build.gradle index ae4ad7c..c04de32 100644 --- a/client/build.gradle +++ b/client/build.gradle @@ -11,11 +11,11 @@ dependencies { //noinspection GradlePackageUpdate api("net.java.jinput:jinput:2.0.5") //noinspection GradlePackageUpdate - api("org.lwjgl.lwjgl:lwjgl:2.9.1") + api("org.lwjgl.lwjgl:lwjgl:2.9.3") //noinspection GradlePackageUpdate - api("org.lwjgl.lwjgl:lwjgl_util:2.9.1") + api("org.lwjgl.lwjgl:lwjgl_util:2.9.3") //noinspection GradlePackageUpdate - api("org.lwjgl.lwjgl:lwjgl-platform:2.9.1") + api("org.lwjgl.lwjgl:lwjgl-platform:2.9.3") // Do no expose spark APIs to mods implementation(project['spark.dependency'] as String) diff --git a/common/build.gradle b/common/build.gradle index 1016f5c..bfabdcf 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -16,63 +16,14 @@ dependencies { // Dev annotations api 'org.jetbrains:annotations:13.0' - // Do no expose spark APIs to mods - implementation(project['spark.dependency'] as String) -} - -// Stuff for BuildConfig -final File buildConfigSrc = new File(buildDir, - "generated/sources/foxloader/com/fox2code/foxloader/launcher/BuildConfig.java") - -static void generateBuildConfig0(File buildConfigSrc, Project project) { - buildConfigSrc.getParentFile().mkdirs() - final String FOXLOADER_TRANSFORMER_VERSION = - project['foxloader.lastReIndevTransformerChanges'] - final String FOXLOADER_VERSION = project['foxloader.version'] - final String SPARK_DEPENDENCY = project['spark.dependency'] - final String SPARK_VERSION = project['spark.version'] - final String REINDEV_VERSION = project['reindev.version'] - final String CLIENT_URL = project['reindev.clientUrl'] - final String SERVER_URL = project['reindev.serverUrl'] - FileOutputStream fileOutputStream = new FileOutputStream(buildConfigSrc) - try { - PrintStream printStream = new PrintStream(fileOutputStream) - printStream.println("// Auto generated class, do not modify.") - printStream.println("package com.fox2code.foxloader.launcher;") - printStream.println() - printStream.println("public final class BuildConfig {") - printStream.println(" private BuildConfig() {}") - printStream.println( // We don't want devs to patch + decompile every update - " public static final String FOXLOADER_TRANSFORMER_VERSION = \"" + - FOXLOADER_TRANSFORMER_VERSION + "\";") - printStream.println(" public static final String FOXLOADER_VERSION = \"" + FOXLOADER_VERSION + "\";") - printStream.println(" public static final String SPARK_DEPENDENCY = \"" + SPARK_DEPENDENCY + "\";") - printStream.println(" public static final String SPARK_VERSION = \"" + SPARK_VERSION + "\";") - printStream.println(" public static final String REINDEV_VERSION = \"" + REINDEV_VERSION + "\";") - printStream.println(" public static final String CLIENT_URL = \"" + CLIENT_URL + "\";") - printStream.println(" public static final String SERVER_URL = \"" + SERVER_URL + "\";") - printStream.println("}") - } finally { - fileOutputStream.close() + // Unpick transformer helper + compileOnly('org.lwjgl.lwjgl:lwjgl:2.9.3') { + transitive = false } -} -tasks.register('generateBuildConfig') { - doLast { - generateBuildConfig0(buildConfigSrc, project) - } -} - -if (!buildConfigSrc.exists()) { - generateBuildConfig0(buildConfigSrc, project) + // Do no expose spark APIs to mods + implementation(project['spark.dependency'] as String) } -compileJava.dependsOn('generateBuildConfig') - -sourceSets { - main { - java { - srcDir 'build/generated/sources/foxloader' - } - } -} \ No newline at end of file +// Generated code is in another script to make code cleaner. +apply from: "generate.gradle" diff --git a/common/generate.gradle b/common/generate.gradle new file mode 100644 index 0000000..e90b9db --- /dev/null +++ b/common/generate.gradle @@ -0,0 +1,258 @@ +import java.lang.reflect.Field +import java.lang.reflect.Modifier + +buildscript { + repositories { + mavenCentral() + } + dependencies { + // Unpick transformer helper + classpath('org.lwjgl.lwjgl:lwjgl:2.9.3') { + transitive = false + } + } +} + +sourceSets { + main { + java { + srcDir 'build/generated/sources/foxloader' + } + } +} + +// BuildConfig stuff +final File buildConfigSrc = new File(buildDir, + "generated/sources/foxloader/com/fox2code/foxloader/launcher/BuildConfig.java") + +static void generateBuildConfig0(File buildConfigSrc, Project project) { + buildConfigSrc.getParentFile().mkdirs() + final String FOXLOADER_TRANSFORMER_VERSION = + project['foxloader.lastReIndevTransformerChanges'] + final String FOXLOADER_VERSION = project['foxloader.version'] + final String SPARK_DEPENDENCY = project['spark.dependency'] + final String SPARK_VERSION = project['spark.version'] + final String REINDEV_VERSION = project['reindev.version'] + final String CLIENT_URL = project['reindev.clientUrl'] + final String SERVER_URL = project['reindev.serverUrl'] + FileOutputStream fileOutputStream = new FileOutputStream(buildConfigSrc) + try { + PrintStream printStream = new PrintStream(fileOutputStream) + printStream.println("// Auto generated class, do not modify.") + printStream.println("package com.fox2code.foxloader.launcher;") + printStream.println() + printStream.println("public final class BuildConfig {") + printStream.println(" private BuildConfig() {}") + printStream.println( // We don't want devs to patch + decompile every update + " public static final String FOXLOADER_TRANSFORMER_VERSION = \"" + + FOXLOADER_TRANSFORMER_VERSION + "\";") + printStream.println(" public static final String FOXLOADER_VERSION = \"" + FOXLOADER_VERSION + "\";") + printStream.println(" public static final String SPARK_DEPENDENCY = \"" + SPARK_DEPENDENCY + "\";") + printStream.println(" public static final String SPARK_VERSION = \"" + SPARK_VERSION + "\";") + printStream.println(" public static final String REINDEV_VERSION = \"" + REINDEV_VERSION + "\";") + printStream.println(" public static final String CLIENT_URL = \"" + CLIENT_URL + "\";") + printStream.println(" public static final String SERVER_URL = \"" + SERVER_URL + "\";") + printStream.println("}") + } finally { + fileOutputStream.close() + } +} + +tasks.register('generateBuildConfig') { + doLast { + generateBuildConfig0(buildConfigSrc, project) + } +} + +if (!buildConfigSrc.exists()) { + generateBuildConfig0(buildConfigSrc, project) +} + +compileJava.dependsOn('generateBuildConfig') + +// GeneratedConstantUnpick stuff +final File generatedConstantUnpicksSrc = new File(buildDir, + "generated/sources/foxloader/com/fox2code/foxloader/loader/transformer/GeneratedConstantUnpicks.java") +final String[] openGLSources = new String[]{"GL11", "GL12", "GL13", "GL14", "GL15", "GL20", "GL21"} +final HashSet explicitlyExcludedEntries = new HashSet<>(Arrays.asList( + // Skip Generic entries + "GL11#GL_ZERO", "GL11#GL_ONE", "GL11#GL_TRUE", "GL11#GL_FALSE", "GL11#GL_NONE", "GL11#GL_NO_ERROR", + // Ignore duplicates + "GL11#GL_LOGIC_OP", "GL11#GL_TEXTURE_COMPONENTS", + "GL12#GL_SMOOTH_POINT_SIZE_RANGE", "GL12#GL_SMOOTH_POINT_SIZE_GRANULARITY", + "GL12#GL_SMOOTH_LINE_WIDTH_RANGE", "GL12#GL_SMOOTH_LINE_WIDTH_GRANULARITY", + "GL15#GL_FOG_COORD_SRC", "GL15#GL_FOG_COORD", "GL15#GL_CURRENT_FOG_COORD", + "GL15#GL_FOG_COORD_ARRAY_TYPE", "GL15#GL_FOG_COORD_ARRAY_STRIDE", + "GL15#GL_FOG_COORD_ARRAY_POINTER", "GL15#GL_FOG_COORD_ARRAY", + "GL15#GL_FOG_COORD_ARRAY_BUFFER_BINDING", + "GL15#GL_SRC0_RGB", "GL15#GL_SRC1_RGB", "GL15#GL_SRC2_RGB", + "GL15#GL_SRC0_ALPHA", "GL15#GL_SRC1_ALPHA", "GL15#GL_SRC2_ALPHA", + "GL20#GL_BLEND_EQUATION_RGB", +)) + +void verifyConstantUnpickCollisions0(String[] openGLSources, HashSet explicitlyExcludedEntries) { + final String RESET = "\033[0m"; + final String RED = "\033[0;31m"; + final String GREEN = "\033[0;32m"; + final String YELLOW = "\033[0;33m"; + final String BLUE = "\033[0;34m"; + HashMap integerHashSet = new HashMap<>() + int entryCount = 0 + boolean configValid = true + for (String openGLSource : openGLSources) { + Class aClass = Class.forName("org.lwjgl.opengl." + openGLSource) + for (Field field : aClass.getDeclaredFields()) { + if ((field.getModifiers() & (Modifier.PUBLIC | Modifier.STATIC)) + != (Modifier.PUBLIC | Modifier.STATIC)) continue + if (field.getName().endsWith("_BIT")) continue + if (field.getType() != int.class) continue + Integer value = Integer.valueOf(field.getInt(null)) + final String entryName = openGLSource + "#" + field.getName() + if (explicitlyExcludedEntries.contains(entryName)) continue + String collision = integerHashSet.putIfAbsent(value, entryName) + if (collision != null) { + project.println(RED + "Collision detected: " + collision + " == " + entryName + RESET) + configValid = false + } + entryCount++ + } + } + for (String exclusion : explicitlyExcludedEntries) { + int i = exclusion.indexOf('#') + if (i == -1) { + project.println(YELLOW + "Exclusion does not contain class name: " + exclusion + RESET) + continue + } + Class aClass = Class.forName("org.lwjgl.opengl." + exclusion.substring(0, i)) + try { + Field field = aClass.getDeclaredField(exclusion.substring(i + 1)) + String collision = integerHashSet.get(field.getInt(null)) + if (collision == null) { + project.println(YELLOW + "No collision found fo exclusion: " + exclusion + RESET) + } else { + project.println(BLUE + "Exclusion " + exclusion + GREEN + + " is good" + BLUE + " because of " + collision + RESET) + } + } catch (Exception e) { + project.println(YELLOW + "Field no longer exists for exclusion: " + exclusion + RESET) + } + } + if (!configValid) { + project.println(RED + "Config is not valid because of collisions!" + RESET) + } else { + project.println(GREEN + "Config has " + entryCount + " entries!" + RESET) + } +} + +static void generateUnpickStart(PrintStream printStream) { + printStream.println(" @Override") + printStream.println(" public FieldInsnNode unpick(int value) {") + printStream.println(" //noinspection DanglingJavadoc") + printStream.println(" switch(value) {") + printStream.println(" default:") + printStream.println(" return null;") +} + +static void generateUnpickEnd(PrintStream printStream) { + printStream.println(" }") + printStream.println(" }") +} + +static void generatedConstantUnpick0(File generatedConstantUnpicksSrc, String[] openGLSources, + HashSet explicitlyExcludedEntries) { + generatedConstantUnpicksSrc.getParentFile().mkdirs() + FileOutputStream fileOutputStream = new FileOutputStream(generatedConstantUnpicksSrc) + try { + PrintStream printStream = new PrintStream(fileOutputStream) + printStream.println("// Auto generated class, do not modify.") + printStream.println("package com.fox2code.foxloader.loader.transformer;") + printStream.println() + printStream.println("import static org.objectweb.asm.Opcodes.GETSTATIC;") + printStream.println("import org.objectweb.asm.tree.FieldInsnNode;") + printStream.println() + for (String openGLSource : openGLSources) { + printStream.println("import org.lwjgl.opengl." + openGLSource + ";") + } + printStream.println("import org.lwjgl.input.Keyboard;") + printStream.println() + printStream.println("public final class GeneratedConstantUnpicks {") + printStream.println(" private GeneratedConstantUnpicks() {}") + printStream.println() + printStream.println(" public static final DevelopmentSourceTransformer.ConstantUnpick " + + "openGLConstantUnpick = new OpenGLConstantUnpick();") + printStream.println(" public static final DevelopmentSourceTransformer.ConstantUnpick " + + "keyboardConstantUnpick = new KeyboardConstantUnpick();") + // OpenGL + printStream.println() + printStream.println(" // Auto generated class, do not modify.") + printStream.println(" private static final class OpenGLConstantUnpick") + printStream.println(" extends DevelopmentSourceTransformer.GeneratedStaticConstantUnpick {") + for (String openGLSource : openGLSources) { + printStream.println(" private static final String " + openGLSource + + " = \"org/lwjgl/opengl/" + openGLSource + "\";") + } + printStream.println() + generateUnpickStart(printStream) + for (String openGLSource : openGLSources) { + Class aClass = Class.forName("org.lwjgl.opengl." + openGLSource) + for (Field field : aClass.getDeclaredFields()) { + if ((field.getModifiers() & (Modifier.PUBLIC | Modifier.STATIC)) + != (Modifier.PUBLIC | Modifier.STATIC)) continue + if (field.getName().endsWith("_BIT")) continue + if (field.getType() != int.class) continue + final String entryName = openGLSource + "#" + field.getName() + if (explicitlyExcludedEntries.contains(entryName)) continue + int value = field.getInt(null) + printStream.println(" case " + value + ": /** {@link " + entryName + "} */") + printStream.println(" return " + + "new FieldInsnNode(GETSTATIC, " + openGLSource + ", \"" + field.getName() + "\", \"I\");") + } + } + generateUnpickEnd(printStream) + printStream.println(" }") + // KeyBoard + printStream.println() + printStream.println(" // Auto generated class, do not modify.") + printStream.println(" private static final class KeyboardConstantUnpick") + printStream.println(" extends DevelopmentSourceTransformer.GeneratedStaticConstantUnpick {") + Class aClass = Class.forName("org.lwjgl.input.Keyboard") + printStream.println(" private static final String KEYBOARD = \"org/lwjgl/input/Keyboard\";") + printStream.println() + generateUnpickStart(printStream) + for (Field field : aClass.getDeclaredFields()) { + if ((field.getModifiers() & (Modifier.PUBLIC | Modifier.STATIC)) + != (Modifier.PUBLIC | Modifier.STATIC)) continue + if (!field.getName().startsWith("KEY_")) continue + if (field.getName().endsWith("WIN")) continue + if (field.getType() != int.class) continue + int value = field.getInt(null) + printStream.println(" case " + value + ": /** {@link Keyboard#" + field.getName() + "} */") + printStream.println(" return " + + "new FieldInsnNode(GETSTATIC, KEYBOARD, \"" + field.getName() + "\", \"I\");") + } + generateUnpickEnd(printStream) + printStream.println(" }") + // End + printStream.println("}") + } finally { + fileOutputStream.close() + } +} + +tasks.register('verifyConstantUnpickCollisions') { + doLast { + verifyConstantUnpickCollisions0(openGLSources, explicitlyExcludedEntries) + } +} + +tasks.register('generateConstantUnpicksSrc') { + doLast { + generatedConstantUnpick0(generatedConstantUnpicksSrc, openGLSources, explicitlyExcludedEntries) + } +} + +if (!generatedConstantUnpicksSrc.exists()) { + generatedConstantUnpick0(generatedConstantUnpicksSrc, openGLSources, explicitlyExcludedEntries) +} + +compileJava.dependsOn('generateConstantUnpicksSrc') diff --git a/common/src/main/java/com/fox2code/foxloader/loader/PreLoader.java b/common/src/main/java/com/fox2code/foxloader/loader/PreLoader.java index 8ca31b3..b204b84 100644 --- a/common/src/main/java/com/fox2code/foxloader/loader/PreLoader.java +++ b/common/src/main/java/com/fox2code/foxloader/loader/PreLoader.java @@ -12,6 +12,7 @@ import java.io.*; import java.net.MalformedURLException; import java.net.URL; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -29,10 +30,14 @@ public class PreLoader { private static final PreLoadMetaJarHash preLoadMetaJarHash = new PreLoadMetaJarHash(); private static ClassDataProvider classDataProviderOverride; private static boolean prePatchInitialized; + private static final String metaInfPath = "META-INF/MANIFEST.MF"; + private static final byte[] metaInf = ("Manifest-Version: 1.0\n" + + "FoxLoader-Transformer-Version: " + BuildConfig.FOXLOADER_TRANSFORMER_VERSION + + "Multi-Release: true\n").getBytes(StandardCharsets.UTF_8); static { preLoadMetaJarHash.addString(BuildConfig.REINDEV_VERSION); - preLoadMetaJarHash.addString(BuildConfig.FOXLOADER_VERSION); + preLoadMetaJarHash.addString(BuildConfig.FOXLOADER_TRANSFORMER_VERSION); FoxClassLoader foxClassLoader = FoxLauncher.getFoxClassLoader(); if (foxClassLoader != null) { // foxClassLoader is null in dev mode foxClassLoader.addClassTransformers((bytes, className) -> { @@ -96,6 +101,7 @@ static void initializePrePatch(boolean client) { static void loadPrePatches(boolean client) { preTransformers.clear(); + registerPrePatch(new VarNameTransformer()); registerPrePatch(new RegistryTransformer()); if (client) { registerPrePatch(new FrustrumHelperTransformer()); @@ -109,11 +115,20 @@ public static void patchReIndevForDev(File in, File out, boolean client) throws throw new IllegalStateException("Not in development environment!"); loadPrePatches(client); registerPrePatch(new DevelopmentModeTransformer()); - patchJar(in, out); + patchJar(in, out, false); } - private static void patchJar(File in, File out) throws IOException { + public static void patchDevReIndevForSource(File in, File out) throws IOException { + if (FoxLauncher.getFoxClassLoader() != null) + throw new IllegalStateException("Not in development environment!"); + preTransformers.clear(); + registerPrePatch(new DevelopmentSourceTransformer()); + patchJar(in, out, true); + } + + private static void patchJar(File in, File out, boolean ignoreFrames) throws IOException { LinkedHashMap hashMap = new LinkedHashMap<>(); + hashMap.put(metaInfPath, metaInf); // Set META-INF first final byte[] empty = new byte[0]; final byte[] buffer = new byte[2048]; ZipEntry entry; @@ -144,9 +159,12 @@ private static void patchJar(File in, File out) throws IOException { entryName.length() - 6).replace('/', '.'); ClassReader classReader = new ClassReader(element.getValue()); ClassNode classNode = new ClassNode(); - classReader.accept(classNode, 0); + classReader.accept(classNode, + ignoreFrames ? ClassReader.SKIP_FRAMES : 0); patchForMixin(classNode, entryName); - ClassWriter classWriter = classDataProviderOverride.newClassWriter(); + ClassWriter classWriter = ignoreFrames ? + new ClassWriter(ClassWriter.COMPUTE_MAXS) : + classDataProviderOverride.newClassWriter(); classNode.accept(classWriter); element.setValue(classWriter.toByteArray()); } diff --git a/common/src/main/java/com/fox2code/foxloader/loader/transformer/DevelopmentModeTransformer.java b/common/src/main/java/com/fox2code/foxloader/loader/transformer/DevelopmentModeTransformer.java index 0fd3c80..224067c 100644 --- a/common/src/main/java/com/fox2code/foxloader/loader/transformer/DevelopmentModeTransformer.java +++ b/common/src/main/java/com/fox2code/foxloader/loader/transformer/DevelopmentModeTransformer.java @@ -1,6 +1,8 @@ package com.fox2code.foxloader.loader.transformer; +import org.objectweb.asm.Type; import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.MethodNode; import java.util.HashMap; @@ -30,5 +32,14 @@ public void transform(ClassNode classNode, String className) { if (registeredInterface != null) { classNode.interfaces.add(registeredInterface.replace('.', '/')); } + for (MethodNode methodNode : classNode.methods) { + final Type[] args = Type.getArgumentTypes(methodNode.desc); + String desc; + if (args.length != 0 && ((desc = args[args.length - 1] + .getDescriptor()).equals("[Ljava/lang/Object;") || + desc.equals("[Ljava/lang/String;"))) { + methodNode.access |= ACC_VARARGS; + } + } } } diff --git a/common/src/main/java/com/fox2code/foxloader/loader/transformer/DevelopmentSourceTransformer.java b/common/src/main/java/com/fox2code/foxloader/loader/transformer/DevelopmentSourceTransformer.java new file mode 100644 index 0000000..a309567 --- /dev/null +++ b/common/src/main/java/com/fox2code/foxloader/loader/transformer/DevelopmentSourceTransformer.java @@ -0,0 +1,615 @@ +package com.fox2code.foxloader.loader.transformer; + +import org.lwjgl.opengl.GL11; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.*; +import org.objectweb.asm.util.Textifier; + +import java.awt.*; +import java.util.HashMap; +import java.util.LinkedList; + +public class DevelopmentSourceTransformer implements PreClassTransformer { + private static final HashMap staticConstantUnpicks = new HashMap<>(); + private static final HashMap virtualConstantUnpicks = new HashMap<>(); + private static final HashMap returnStaticConstantUnpicks = new HashMap<>(); + private static final HashMap putStaticConstantUnpicks = new HashMap<>(); + private static final ConstantUnpick glColorMaterial; + + static { + // Java Unpicks + ConstantUnpick threadPriorityUnpick = new IntStaticConstantUnpick("java/lang/Thread") { + @Override + public String unpick(int value) { + switch (value) { + default: + return null; + case Thread.MIN_PRIORITY: + return "MIN_PRIORITY"; + case Thread.NORM_PRIORITY: + return "NORM_PRIORITY"; + case Thread.MAX_PRIORITY: + return "MAX_PRIORITY"; + } + } + }; + virtualConstantUnpicks.put("java/lang/Thread.setPriority(I)V", threadPriorityUnpick); + // AWT/Swing Unpicks + ConstantUnpick borderLayoutUnpick = new StringConstantUnpick() { + @Override + public void unpick(InsnList insnList, AbstractInsnNode constant, String value) { + switch (value) { + case BorderLayout.CENTER: + insnList.insert(constant, new FieldInsnNode(GETSTATIC, "java/awt/BorderLayout", "CENTER", "I")); + insnList.remove(constant); + break; + case BorderLayout.NORTH: + insnList.insert(constant, new FieldInsnNode(GETSTATIC, "java/awt/BorderLayout", "NORTH", "I")); + insnList.remove(constant); + break; + case BorderLayout.SOUTH: + insnList.insert(constant, new FieldInsnNode(GETSTATIC, "java/awt/BorderLayout", "SOUTH", "I")); + insnList.remove(constant); + break; + case BorderLayout.EAST: + insnList.insert(constant, new FieldInsnNode(GETSTATIC, "java/awt/BorderLayout", "EAST", "I")); + insnList.remove(constant); + break; + case BorderLayout.WEST: + insnList.insert(constant, new FieldInsnNode(GETSTATIC, "java/awt/BorderLayout", "WEST", "I")); + insnList.remove(constant); + break; + } + } + }; + virtualConstantUnpicks.put("java/awt/Frame.add(Ljava/awt/Component;Ljava/lang/Object;)V", borderLayoutUnpick); + virtualConstantUnpicks.put("javax/swing/JPanel.add(Ljava/awt/Component;Ljava/lang/Object;)V", borderLayoutUnpick); + // Minecraft specific AWT/Swing Unpicks + virtualConstantUnpicks.put("net/minecraft/client/MinecraftApplet.add(Ljava/awt/Component;Ljava/lang/Object;)V", borderLayoutUnpick); + virtualConstantUnpicks.put("net/minecraft/src/server/ServerGUI.add(Ljava/awt/Component;Ljava/lang/Object;)V", borderLayoutUnpick); + // LWJGL/OpenGL Unpicks + ConstantUnpick glAttribBits = new FlagIntStaticConstantUnpick("org/lwjgl/opengl/GL11", + new Flag(GL11.GL_ALL_ATTRIB_BITS, "GL_ALL_ATTRIB_BITS"), + new Flag(GL11.GL_CURRENT_BIT, "GL_CURRENT_BIT"), + new Flag(GL11.GL_POINT_BIT, "GL_POINT_BIT"), + new Flag(GL11.GL_LINE_BIT, "GL_LINE_BIT"), + new Flag(GL11.GL_POLYGON_BIT, "GL_POLYGON_BIT"), + new Flag(GL11.GL_POLYGON_STIPPLE_BIT, "GL_POLYGON_STIPPLE_BIT"), + new Flag(GL11.GL_PIXEL_MODE_BIT, "GL_PIXEL_MODE_BIT"), + new Flag(GL11.GL_LIGHTING_BIT, "GL_LIGHTING_BIT"), + new Flag(GL11.GL_FOG_BIT, "GL_FOG_BIT"), + new Flag(GL11.GL_DEPTH_BUFFER_BIT, "GL_DEPTH_BUFFER_BIT"), + new Flag(GL11.GL_ACCUM_BUFFER_BIT, "GL_ACCUM_BUFFER_BIT"), + new Flag(GL11.GL_STENCIL_BUFFER_BIT, "GL_STENCIL_BUFFER_BIT"), + new Flag(GL11.GL_VIEWPORT_BIT, "GL_VIEWPORT_BIT"), + new Flag(GL11.GL_TRANSFORM_BIT, "GL_TRANSFORM_BIT"), + new Flag(GL11.GL_ENABLE_BIT, "GL_ENABLE_BIT"), + new Flag(GL11.GL_COLOR_BUFFER_BIT, "GL_COLOR_BUFFER_BIT"), + new Flag(GL11.GL_HINT_BIT, "GL_HINT_BIT"), + new Flag(GL11.GL_EVAL_BIT, "GL_EVAL_BIT"), + new Flag(GL11.GL_LIST_BIT, "GL_LIST_BIT"), + new Flag(GL11.GL_TEXTURE_BIT, "GL_TEXTURE_BIT"), + new Flag(GL11.GL_HINT_BIT, "GL_HINT_BIT")); + ConstantUnpick glZeroOne = new IntStaticConstantUnpick("org/lwjgl/opengl/GL11") { + @Override + public String unpick(int value) { + switch (value) { + case GL11.GL_ZERO: + return "GL_ZERO"; + case GL11.GL_ONE: + return "GL_ONE"; + default: + return null; + } + } + }; + ConstantUnpick glParam = GeneratedConstantUnpicks.openGLConstantUnpick; + ConstantUnpick glParamNum = new MultiConstantUnpick(glZeroOne, glParam); + staticConstantUnpicks.put("org/lwjgl/opengl/GL11.glClear(I)V", glAttribBits); + staticConstantUnpicks.put("org/lwjgl/opengl/GL11.glEnable(I)V", glParam); + staticConstantUnpicks.put("org/lwjgl/opengl/GL11.glDisable(I)V", glParam); + staticConstantUnpicks.put("org/lwjgl/opengl/GL11.glEnableClientState(I)V", glParam); + staticConstantUnpicks.put("org/lwjgl/opengl/GL11.glDisableClientState(I)V", glParam); + staticConstantUnpicks.put("org/lwjgl/opengl/GL11.glGetString(I)Ljava/lang/String;", glParam); + staticConstantUnpicks.put("org/lwjgl/opengl/GL11.glBindTexture(II)V", + new ParamsConstantUnpick(glParam, null)); + staticConstantUnpicks.put("org/lwjgl/opengl/GL11.glCullFace(I)V", glParam); + staticConstantUnpicks.put("org/lwjgl/opengl/GL11.glMatrixMode(I)V", glParam); + staticConstantUnpicks.put("org/lwjgl/opengl/GL11.glDepthFunc(I)V", glParam); + staticConstantUnpicks.put("org/lwjgl/opengl/GL11.glBlendFunc(II)V", + new ParamsConstantUnpick(glParamNum, glParamNum)); + staticConstantUnpicks.put("org/lwjgl/opengl/GL11.glAlphaFunc(IF)V", + new ParamsConstantUnpick(glParamNum, null)); + staticConstantUnpicks.put("org/lwjgl/opengl/GL11.glShadeModel(I)V", glParam); + staticConstantUnpicks.put("org/lwjgl/opengl/GL11.glTexImage2D(IIIIIIIILjava/nio/ByteBuffer;)V", + new ParamsConstantUnpick(glParam, null, glParam, null, null, null, glParam, glParam, + new CheckCastConstantUnpick("java/nio/ByteBuffer"))); + staticConstantUnpicks.put("org/lwjgl/opengl/GL11.glTexImage2D(IIIIIIIILjava/nio/IntBuffer;)V", + new ParamsConstantUnpick(glParam, null, glParam, null, null, null, glParam, glParam, + new CheckCastConstantUnpick("java/nio/IntBuffer"))); + staticConstantUnpicks.put("org/lwjgl/opengl/GL11.glTexSubImage2D(IIIIIIIILjava/nio/IntBuffer;)V", + new ParamsConstantUnpick(glParam, null, null, null, null, null, glParam, glParam, + new CheckCastConstantUnpick("java/nio/IntBuffer"))); + staticConstantUnpicks.put("org/lwjgl/opengl/GL11.glTexParameteri(III)V", + new ParamsConstantUnpick(glParam, glParam, glParam)); + staticConstantUnpicks.put("org/lwjgl/opengl/GL11.glGetTexLevelParameteri(III)I", + new ParamsConstantUnpick(glParam, glParam, glParam)); + for (String typeName : new String[]{"Float", "Double", "Integer"}) { + String desc = typeName.substring(0, 1); + String bufName = typeName.equals("Integer") ? "Int" : typeName; + staticConstantUnpicks.put("org/lwjgl/opengl/GL11.glGet" + + typeName + "(ILjava/nio/" + bufName + "Buffer;)V", + new ParamsConstantUnpick(glParam, + new CheckCastConstantUnpick("java/nio/" + bufName + "Buffer"))); + staticConstantUnpicks.put("org/lwjgl/opengl/GL11.glGet" + + typeName + "(I)" + desc, glParam); + } + staticConstantUnpicks.put("org/lwjgl/opengl/GL11.glColorMaterial(II)V", + glColorMaterial = new ParamsConstantUnpick(glParamNum, glParamNum)); + staticConstantUnpicks.put("org/lwjgl/opengl/GL11.glFogi(II)V", + new ParamsConstantUnpick(glParamNum, glParamNum)); + staticConstantUnpicks.put("org/lwjgl/opengl/GL11.glFogf(IF)V", + new ParamsConstantUnpick(glParamNum, null)); + staticConstantUnpicks.put("org/lwjgl/opengl/GL11.glNewList(II)I", + new ParamsConstantUnpick(null, glParam)); + staticConstantUnpicks.put("org/lwjgl/opengl/GL11.glNormalPointer(IIJ)V", + new ParamsConstantUnpick(glParam, null, null)); + staticConstantUnpicks.put("org/lwjgl/opengl/GL11.glVertexPointer(IIIJ)V", + new ParamsConstantUnpick(null, glParam, null, null)); + staticConstantUnpicks.put("org/lwjgl/opengl/GL11.glLight(IILjava/nio/FloatBuffer;)V", + new ParamsConstantUnpick(glParam, glParam, null)); + staticConstantUnpicks.put("org/lwjgl/opengl/GL11.glFog(ILjava/nio/FloatBuffer;)V", + new ParamsConstantUnpick(glParam, null)); + // Minecraft specific LWJGL/OpenGL Unpicks + virtualConstantUnpicks.put("net/minecraft/src/client/renderer/Tessellator.startDrawing(I)V", glParam); + putStaticConstantUnpicks.put("net/minecraft/src/client/renderer/OpenGlHelper2#lightmapDisabled", glParam); + putStaticConstantUnpicks.put("net/minecraft/src/client/renderer/OpenGlHelper2#lightmapEnabled", glParam); + putStaticConstantUnpicks.put("net/minecraft/src/client/renderer/entity/OpenGlHelper#defaultTexUnit", glParam); + putStaticConstantUnpicks.put("net/minecraft/src/client/renderer/entity/OpenGlHelper#lightmapTexUnit", glParam); + // LWJGL/Input Unpicks + ConstantUnpick key = GeneratedConstantUnpicks.keyboardConstantUnpick; + staticConstantUnpicks.put("org/lwjgl/input/Keyboard.isKeyDown(I)Z", key); + returnStaticConstantUnpicks.put("org/lwjgl/input/Keyboard.getEventKey()I", key); + // Minecraft specific LWJGL/Input Unpicks + virtualConstantUnpicks.put("net/minecraft/src/client/KeyBinding.(Ljava/lang/String;I)V", key); + } + + private final StringBuilder testDebug; + + public DevelopmentSourceTransformer() { + this.testDebug = null; + } + + public DevelopmentSourceTransformer(StringBuilder testDebug) { + this.testDebug = testDebug; + } + + @Override + public void transform(ClassNode classNode, String className) { + if (!className.startsWith("net.minecraft.")) return; + for (MethodNode methodNode : classNode.methods) { + final InsnList insnList = methodNode.instructions; + for (AbstractInsnNode abstractInsnNode : insnList) { + final int opcode = abstractInsnNode.getOpcode(); + if (opcode == INVOKESTATIC) { + MethodInsnNode methodInsnNode = (MethodInsnNode) abstractInsnNode; + ConstantUnpick constantUnpick = + staticConstantUnpicks.get(methodInsnNode.owner + "." + + methodInsnNode.name + methodInsnNode.desc); + ConstantUnpick returnConstantUnpick = + returnStaticConstantUnpicks.get(methodInsnNode.owner + "." + + methodInsnNode.name + methodInsnNode.desc); + if (this.testDebug != null && constantUnpick == glColorMaterial) { + this.testDebug.append("Got glColorMaterial!\n"); + constantUnpick = new ParamsConstantUnpick( + GeneratedConstantUnpicks.openGLConstantUnpick, + GeneratedConstantUnpicks.openGLConstantUnpick) + .setTestDebug(this.testDebug); + } + if (constantUnpick != null) { + constantUnpick.unpick(insnList, methodInsnNode.getPrevious()); + } + if (returnConstantUnpick != null) { + AbstractInsnNode insn = methodInsnNode.getNext().getNext(); + if (insn != null) { + switch (insn.getOpcode()) { + case IF_ICMPEQ: + case IF_ICMPNE: + case IF_ICMPLE: + case IF_ICMPGE: + case IF_ICMPLT: + case IF_ICMPGT: + returnConstantUnpick.unpick(insnList, methodInsnNode.getNext()); + } + } + } + } else if (opcode == INVOKEVIRTUAL || opcode == INVOKESPECIAL) { + MethodInsnNode methodInsnNode = (MethodInsnNode) abstractInsnNode; + ConstantUnpick constantUnpick = + virtualConstantUnpicks.get(methodInsnNode.owner + "." + + methodInsnNode.name + methodInsnNode.desc); + if (constantUnpick != null) { + constantUnpick.unpick(insnList, methodInsnNode.getPrevious()); + } + } else if (opcode == PUTSTATIC) { + FieldInsnNode fieldInsnNode = (FieldInsnNode) abstractInsnNode; + ConstantUnpick constantUnpick = + putStaticConstantUnpicks.get(fieldInsnNode.owner + "#" + fieldInsnNode.name); + if (constantUnpick != null) { + constantUnpick.unpick(insnList, fieldInsnNode.getPrevious()); + } + } + } + } + } + + public static abstract class ConstantUnpick { + private StringBuilder testDebug; + + public abstract void unpick(InsnList insnList, AbstractInsnNode constant); + + public final ConstantUnpick setTestDebug(StringBuilder testDebug) { + this.setTestDebugOnChilds(testDebug); + this.testDebug = testDebug; + return this; + } + + void setTestDebugOnChilds(StringBuilder testDebug) {} + + protected final void testDbg(String line) { + if (this.testDebug != null) { + this.testDebug.append(line).append("\n"); + } + } + + protected final void testDbgOpcode(int opcode) { + if (this.testDebug != null) { + this.testDebug.append("Opcode: ").append( + Textifier.OPCODES[opcode]).append("\n"); + } + } + + protected final void testDbgOpcode(String text, int opcode) { + if (this.testDebug != null) { + this.testDebug.append(text).append( + Textifier.OPCODES[opcode]).append("\n"); + } + } + + protected final void npeDbg(InsnList insnList, Object obj) { + if (obj != null) return; + if (this.testDebug == null) throw new NullPointerException(); + StringBuilder stringBuilder = new StringBuilder() + .append("\n---\n").append(this.testDebug).append("\n---\n"); + TransformerUtils.printInsnList(insnList, stringBuilder); + throw new NullPointerException(stringBuilder.append("\n---").toString()); + } + } + + public static final class MultiConstantUnpick extends ConstantUnpick { + private final ConstantUnpick[] constantUnpicks; + + public MultiConstantUnpick(ConstantUnpick... constantUnpicks) { + this.constantUnpicks = constantUnpicks; + } + + @Override + public void unpick(InsnList insnList, AbstractInsnNode constant) { + for (ConstantUnpick constantUnpick : constantUnpicks) { + if (constant.getNext() == null) break; + constantUnpick.unpick(insnList, constant); + } + } + + @Override + void setTestDebugOnChilds(StringBuilder testDebug) { + for (ConstantUnpick constantUnpick : constantUnpicks) { + constantUnpick.setTestDebug(testDebug); + } + } + } + + public static final class ParamsConstantUnpick extends ConstantUnpick { + private final ConstantUnpick[] constantUnpicks; + + public ParamsConstantUnpick(ConstantUnpick... constantUnpicks) { + this.constantUnpicks = constantUnpicks; + } + + @Override + public void unpick(InsnList insnList, AbstractInsnNode constant) { + boolean skipNextUnpick; + for (int i = constantUnpicks.length - 1; i >= 0; i--) { + skipNextUnpick = false; + int loops = 1; + while (loops-->0) { + npeDbg(insnList, constant); // <- for test + testDbgOpcode(constant.getOpcode()); + switch (constant.getOpcode()) { + default: + return; + case INVOKEINTERFACE: + case INVOKEVIRTUAL: + case INVOKESPECIAL: + loops++; + case INVOKESTATIC: { + MethodInsnNode methodInsnNode = (MethodInsnNode) constant; + if (methodInsnNode.desc.endsWith(")V")) return; + loops += Type.getArgumentTypes(methodInsnNode.desc).length; + skipNextUnpick = true; + break; + } + case DUP: + loops--; + break; + case IADD: + case ISUB: + case IMUL: + case IDIV: + case FADD: + case FSUB: + case FMUL: + case FDIV: + case DADD: + case DSUB: + case DMUL: + case DDIV: + case IAND: + case IOR: + case IXOR: + case ISHL: + case ISHR: + case IUSHR: + loops++; + case ARRAYLENGTH: + case GETFIELD: + case INEG: + case FNEG: + case DNEG: + case I2D: + case I2F: + case F2I: + case D2I: + case D2F: + case L2D: + case L2I: + case L2F: + case I2L: + case D2L: + case F2L: + skipNextUnpick = true; + case CHECKCAST: + case I2B: + case I2C: + case I2S: + loops++; + case GETSTATIC: + case ACONST_NULL: + case ICONST_M1: + case ICONST_0: + case ICONST_1: + case ICONST_2: + case ICONST_3: + case ICONST_4: + case ICONST_5: + case FCONST_0: + case FCONST_1: + case FCONST_2: + case DCONST_0: + case DCONST_1: + case LCONST_0: + case LCONST_1: + case BIPUSH: + case SIPUSH: + case LDC: + case ALOAD: + case ILOAD: + case FLOAD: + case DLOAD: + } + if (loops > 0) { + constant = constant.getPrevious(); + } + testDbg("Loops left: " + loops + " skip next: " + skipNextUnpick); + } + loops++; + if (loops < 0) { + testDbg("Neg loop: " + loops); + i += loops; + skipNextUnpick = true; + } + ConstantUnpick constantUnpick = constantUnpicks[i]; + testDbg("Unpick: " + i + "/" + constantUnpicks.length + " opcode: " + Textifier.OPCODES[constant.getOpcode()]); + if (constantUnpick != null && !skipNextUnpick) { + testDbg("Processing..."); + AbstractInsnNode previous = constant.getPrevious(); + constantUnpick.unpick(insnList, constant); + if (previous == null || + (constant = previous.getNext()) == null) { + return; + } + } + constant = constant.getPrevious(); + } + } + + @Override + void setTestDebugOnChilds(StringBuilder testDebug) { + for (ConstantUnpick constantUnpick : constantUnpicks) { + if (constantUnpick != null) { + constantUnpick.setTestDebug(testDebug); + } + } + } + } + + public static abstract class IntConstantUnpick extends ConstantUnpick { + @Override + public final void unpick(InsnList insnList, AbstractInsnNode constant) { + switch (constant.getOpcode()) { + case ICONST_M1: + unpick(insnList, constant, -1); + break; + case ICONST_0: + unpick(insnList, constant, 0); + break; + case ICONST_1: + unpick(insnList, constant, 1); + break; + case ICONST_2: + unpick(insnList, constant, 2); + break; + case ICONST_3: + unpick(insnList, constant, 3); + break; + case ICONST_4: + unpick(insnList, constant, 4); + break; + case ICONST_5: + unpick(insnList, constant, 5); + break; + case BIPUSH: + case SIPUSH: + unpick(insnList, constant, ((IntInsnNode) constant).operand); + break; + case LDC: + Object ldc = ((LdcInsnNode) constant).cst; + if (ldc instanceof Integer) { + unpick(insnList, constant, (Integer) ldc); + } + } + } + + public abstract void unpick(InsnList insnList, AbstractInsnNode constant, int value); + } + + private static abstract class StringConstantUnpick extends ConstantUnpick { + @Override + public void unpick(InsnList insnList, AbstractInsnNode constant) { + if (constant.getOpcode() == LDC) { + Object ldc = ((LdcInsnNode) constant).cst; + if (ldc instanceof String) { + unpick(insnList, constant, (String) ldc); + } + } + } + + public abstract void unpick(InsnList insnList, AbstractInsnNode constant, String value); + } + + public static final class CheckCastConstantUnpick extends ConstantUnpick { + private final String type; + + private CheckCastConstantUnpick(String type) { + this.type = type; + } + + @Override + public void unpick(InsnList insnList, AbstractInsnNode constant) { + if (constant.getOpcode() == ACONST_NULL && + // For ParamsConstantUnpick compatibility we must + // also check if next instruction is not a check-cast + constant.getNext().getOpcode() != CHECKCAST) { + insnList.insert(constant, new TypeInsnNode(CHECKCAST, this.type)); + } + } + } + + public static abstract class GeneratedStaticConstantUnpick extends IntConstantUnpick { + public GeneratedStaticConstantUnpick() {} + + @Override + public void unpick(InsnList insnList, AbstractInsnNode constant, int value) { + FieldInsnNode fieldInsnNode = unpick(value); + if (fieldInsnNode != null) { + insnList.insert(constant, fieldInsnNode); + insnList.remove(constant); + testDbg("Found field " + fieldInsnNode.name + " for value " + value); + } else { + testDbg("Failed to find field for value " + value); + } + } + + public abstract FieldInsnNode unpick(int value); + } + + public static abstract class IntStaticConstantUnpick extends IntConstantUnpick { + private final String owner; + + public IntStaticConstantUnpick(String owner) { + this.owner = owner; + } + + @Override + public void unpick(InsnList insnList, AbstractInsnNode constant, int value) { + String fieldName = unpick(value); + if (fieldName != null) { + insnList.insert(constant, + new FieldInsnNode(GETSTATIC, this.owner, fieldName, "I")); + insnList.remove(constant); + } + } + + public abstract String unpick(int value); + } + + public static class FlagIntStaticConstantUnpick extends IntConstantUnpick { + private final String defaultOwner; + private final Flag[] flags; + + public FlagIntStaticConstantUnpick(String defaultOwner, Flag... flags) { + this.defaultOwner = defaultOwner; + this.flags = flags; + } + + @Override + public void unpick(InsnList insnList, AbstractInsnNode constant, int value) { + if (value == 0) return; + LinkedList flags = new LinkedList<>(); + for (Flag flag : this.flags) { + if ((flag.value & value) == flag.value) { + value &= ~flag.value; + flags.add(flag); + if (value == 0) + break; + } + } + if (flags.isEmpty()) return; + boolean first = true; + for (Flag flag : flags) { + String owner = this.defaultOwner; + if (flag.owner != null) owner = flag.owner; + insnList.insertBefore(constant, new FieldInsnNode(GETSTATIC, owner, flag.name, "I")); + if (first) { + first = false; + } else { + insnList.insertBefore(constant, new InsnNode(IOR)); + } + } + if (value != 0) { + insnList.insertBefore(constant, TransformerUtils.getNumberInsn(value)); + insnList.insertBefore(constant, new InsnNode(IOR)); + } + insnList.remove(constant); + } + + } + + public static class Flag { + public final int value; + public final String owner; + public final String name; + + Flag(int value, String owner, String name) { + this.value = value; + this.owner = owner; + this.name = name; + } + + Flag(int value, String name) { + this.value = value; + this.owner = null; + this.name = name; + } + } +} diff --git a/common/src/main/java/com/fox2code/foxloader/loader/transformer/TransformerUtils.java b/common/src/main/java/com/fox2code/foxloader/loader/transformer/TransformerUtils.java index d37daa9..c1488b6 100644 --- a/common/src/main/java/com/fox2code/foxloader/loader/transformer/TransformerUtils.java +++ b/common/src/main/java/com/fox2code/foxloader/loader/transformer/TransformerUtils.java @@ -1,8 +1,15 @@ package com.fox2code.foxloader.loader.transformer; import com.fox2code.foxloader.launcher.ClassTransformer; +import org.jetbrains.annotations.NotNull; +import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.*; +import org.objectweb.asm.util.Textifier; +import org.objectweb.asm.util.TraceMethodVisitor; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.Writer; import java.util.ArrayList; import java.util.IdentityHashMap; import java.util.Map; @@ -77,4 +84,37 @@ public static FieldNode getField(ClassNode classNode,String fieldName) { } throw new NoSuchElementException(classNode.name + "." + fieldName); } + + public static AbstractInsnNode getNumberInsn(int number) { + if (number >= -1 && number <= 5) + return new InsnNode(number + 3); + else if (number >= -128 && number <= 127) + return new IntInsnNode(Opcodes.BIPUSH, number); + else if (number >= -32768 && number <= 32767) + return new IntInsnNode(Opcodes.SIPUSH, number); + else + return new LdcInsnNode(number); + } + + public static String printInsnList(InsnList insnList) { + final StringBuilder stringBuilder = new StringBuilder(); + printInsnList(insnList, stringBuilder); + return stringBuilder.toString(); + } + + public static void printInsnList(final InsnList insnList,final StringBuilder stringBuilder) { + Textifier textifier = new Textifier(); + MethodNode methodNode = new MethodNode(0, "insns", "()V", null, null); + methodNode.instructions = insnList; + methodNode.accept(new TraceMethodVisitor(textifier)); + textifier.print(new PrintWriter(new Writer() { + @Override + public void write(@NotNull char[] cbuf, int off, int len) throws IOException { + stringBuilder.append(cbuf, off, len); + } + + @Override public void flush() {} + @Override public void close() {} + })); + } } diff --git a/common/src/main/java/com/fox2code/foxloader/loader/transformer/VarNameTransformer.java b/common/src/main/java/com/fox2code/foxloader/loader/transformer/VarNameTransformer.java new file mode 100644 index 0000000..3061c08 --- /dev/null +++ b/common/src/main/java/com/fox2code/foxloader/loader/transformer/VarNameTransformer.java @@ -0,0 +1,85 @@ +package com.fox2code.foxloader.loader.transformer; + +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.LocalVariableNode; +import org.objectweb.asm.tree.MethodNode; + +import java.util.HashSet; +import java.util.Locale; + +public class VarNameTransformer implements PreClassTransformer { + @Override + public void transform(ClassNode classNode, String className) { + if (!className.startsWith("net.minecraft.")) return; + for (MethodNode methodNode : classNode.methods) { + if (methodNode.localVariables != null) { + int maxArgIndex = (methodNode.access & ACC_STATIC) == 0 ? 1 : 0; + for (Type arg : Type.getArgumentTypes(methodNode.desc)) { + maxArgIndex += arg.getSize(); + } + HashSet reservedNames = new HashSet<>(); + reservedNames.add("this"); + for (LocalVariableNode localVariableNode : methodNode.localVariables) { + if (localVariableNode.index == 0 && + (methodNode.access & ACC_STATIC) == 0) { + localVariableNode.name = "this"; + continue; + } + reservedNames.add(localVariableNode.name); + } + for (LocalVariableNode localVariableNode : methodNode.localVariables) { + if (localVariableNode.index == 0 && + (methodNode.access & ACC_STATIC) == 0) { + continue; + } + String varName = localVariableNode.name; + if (varName.length() < 4 || + !varName.startsWith("var") || + !Character.isDigit(varName.charAt(3))) { + continue; + } + varName = null; + final String desc = localVariableNode.desc; + if (desc.startsWith("L") && !desc.startsWith("Ljava/lang/")) { + int i = Math.max(desc.lastIndexOf('/') + 1, 1); + if (desc.charAt(i) == 'I' && + Character.isUpperCase(desc.charAt(i + 1))) { + i++; + } + varName = localVariableNode.desc.substring(i, i + 1).toLowerCase(Locale.ROOT) + + localVariableNode.desc.substring(i + 1, desc.length() - 1); + } else if (desc.equals("[Ljava/lang/Object;")) { + if (localVariableNode.index == (maxArgIndex - 1)) + varName = "args"; + } else if (desc.startsWith("[L")) { + int i = Math.max(desc.lastIndexOf('/') + 1, 2); + if (desc.charAt(i) == 'I' && + Character.isUpperCase(desc.charAt(i + 1))) { + i++; + } + varName = localVariableNode.desc.substring(i, i + 1).toLowerCase(Locale.ROOT) + + localVariableNode.desc.substring(i + 1, desc.length() - 1); + if (varName.endsWith("s")) { + if (!varName.endsWith("es")) { + varName += "es"; + } + } else { + varName += "s"; + } + } + if (varName != null && !reservedNames.contains(varName)) { + reservedNames.add(varName); + localVariableNode.name = varName; + } else if (localVariableNode.index < maxArgIndex) { + varName = "arg" + localVariableNode.index; + if (!reservedNames.contains(varName)) { + reservedNames.add(varName); + localVariableNode.name = varName; + } + } + } + } + } + } +} diff --git a/common/src/test/java/com/fox2code/foxloader/testes/DevelopmentSourceTransformerTests.java b/common/src/test/java/com/fox2code/foxloader/testes/DevelopmentSourceTransformerTests.java new file mode 100644 index 0000000..dfb2004 --- /dev/null +++ b/common/src/test/java/com/fox2code/foxloader/testes/DevelopmentSourceTransformerTests.java @@ -0,0 +1,107 @@ +package com.fox2code.foxloader.testes; + +import static com.fox2code.foxloader.loader.transformer.DevelopmentSourceTransformer.*; + +import com.fox2code.foxloader.loader.transformer.DevelopmentSourceTransformer; +import com.fox2code.foxloader.loader.transformer.GeneratedConstantUnpicks; +import com.fox2code.foxloader.loader.transformer.TransformerUtils; +import org.junit.jupiter.api.Test; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.*; + +public class DevelopmentSourceTransformerTests implements Opcodes { + public DevelopmentSourceTransformerTests() { + new ParamsConstantUnpick(); + } + + @Test + public void testParamsUnpickNoop() { + InsnList insnList = new InsnList(); + StringBuilder testDebug = new StringBuilder(); + AbstractInsnNode constant; + insnList.add(constant = TransformerUtils.getNumberInsn(2929)); + insnList.add(new MethodInsnNode(INVOKESTATIC, "org/lwjgl/opengl/GL11", "glEnable", "(I)V")); + insnList.add(new InsnNode(RETURN)); + new ParamsConstantUnpick(GeneratedConstantUnpicks.openGLConstantUnpick) + .setTestDebug(testDebug).unpick(insnList, constant); + assertUnpicked(insnList, constant, testDebug); + } + + @Test + public void testParamsUnpickMono() { + InsnList insnList = new InsnList(); + StringBuilder testDebug = new StringBuilder(); + AbstractInsnNode constant; + AbstractInsnNode ldc; + insnList.add(constant = TransformerUtils.getNumberInsn(516)); + insnList.add(ldc = new LdcInsnNode(0.1F)); + insnList.add(new MethodInsnNode(INVOKESTATIC, "org/lwjgl/opengl/GL11", "glAlphaFunc", "(IF)V")); + insnList.add(new InsnNode(RETURN)); + new ParamsConstantUnpick(GeneratedConstantUnpicks.openGLConstantUnpick, null) + .setTestDebug(testDebug).unpick(insnList, ldc); + assertUnpicked(insnList, constant, testDebug); + } + + @Test + public void testParamsUnpickDuo() { + InsnList insnList = new InsnList(); + StringBuilder testDebug = new StringBuilder(); + AbstractInsnNode constant0, constant1; + insnList.add(constant0 = TransformerUtils.getNumberInsn(1032)); + insnList.add(constant1 = TransformerUtils.getNumberInsn(5634)); + insnList.add(new MethodInsnNode(INVOKESTATIC, "org/lwjgl/opengl/GL11", "glColorMaterial", "(II)V")); + insnList.add(new InsnNode(RETURN)); + new ParamsConstantUnpick(GeneratedConstantUnpicks.openGLConstantUnpick, + GeneratedConstantUnpicks.openGLConstantUnpick) + .setTestDebug(testDebug).unpick(insnList, constant1); + assertUnpicked(insnList, constant0, testDebug); + assertUnpicked(insnList, constant1, testDebug); + } + + @Test + public void testParamsUnpickDuoNested() { + ClassNode classNode = new ClassNode(); + MethodNode methodNode = new MethodNode(); + classNode.methods.add(methodNode); + InsnList insnList = methodNode.instructions; + StringBuilder testDebug = new StringBuilder(); + AbstractInsnNode constant0, constant1; + insnList.add(constant0 = TransformerUtils.getNumberInsn(1032)); + insnList.add(constant1 = TransformerUtils.getNumberInsn(5634)); + insnList.add(new MethodInsnNode(INVOKESTATIC, "org/lwjgl/opengl/GL11", "glColorMaterial", "(II)V")); + insnList.add(new InsnNode(RETURN)); + new DevelopmentSourceTransformer(testDebug).transform(classNode, "net.minecraft.Class"); + assertUnpicked(insnList, constant0, testDebug); + assertUnpicked(insnList, constant1, testDebug); + } + + @Test + public void testParamsUnpickBehindMethod() { + InsnList insnList = new InsnList(); + StringBuilder testDebug = new StringBuilder(); + AbstractInsnNode constant, notConstant; + insnList.add(constant = TransformerUtils.getNumberInsn(3553)); + insnList.add(new VarInsnNode(ALOAD, 0)); + insnList.add(new FieldInsnNode(GETFIELD, "net/minecraft/client/Minecraft", + "renderEngine", "Lnet/minecraft/src/client/renderer/RenderEngine;")); + insnList.add(new LdcInsnNode("/title/mojang.png")); + insnList.add(notConstant = new MethodInsnNode(INVOKEVIRTUAL, "net/minecraft/src/client/renderer/RenderEngine", "getTexture", "(Ljava/lang/String;)I")); + insnList.add(new MethodInsnNode(INVOKESTATIC, "org/lwjgl/opengl/GL11", "glColorMaterial", "(II)V")); + insnList.add(new InsnNode(RETURN)); + new ParamsConstantUnpick(GeneratedConstantUnpicks.openGLConstantUnpick, null) + .setTestDebug(testDebug).unpick(insnList, notConstant); + assertUnpicked(insnList, constant, testDebug); + } + + public void assertUnpicked(InsnList insnList, AbstractInsnNode constant, StringBuilder testDebug) { + if (constant.getNext() != null) { + StringBuilder stringBuilder = new StringBuilder(256).append("Constant wasn't inlined!\n---\n"); + if (testDebug != null) { + stringBuilder.append(testDebug).append("\n---\n"); + } + TransformerUtils.printInsnList(insnList, stringBuilder); + stringBuilder.append("\n---"); + throw new AssertionError(stringBuilder.toString()); + } + } +} diff --git a/dev/build.gradle b/dev/build.gradle index 1ba67d5..8e759cf 100644 --- a/dev/build.gradle +++ b/dev/build.gradle @@ -18,6 +18,11 @@ dependencies { api 'com.github.QuiltMC:quiltflower:1.8.1' api 'com.google.code.gson:gson:2.2.4' api 'org.ow2.asm:asm-commons:9.4' + api 'org.ow2.asm:asm-util:9.4' + // Need LWJGL 2 for game decompilation. + api('org.lwjgl.lwjgl:lwjgl:2.9.3') { + transitive = false + } include(project(":common")) { transitive = false } diff --git a/dev/src/main/groovy/com/fox2code/foxloader/dev/FoxLoaderDecompiler.java b/dev/src/main/groovy/com/fox2code/foxloader/dev/FoxLoaderDecompiler.java new file mode 100644 index 0000000..18fbcca --- /dev/null +++ b/dev/src/main/groovy/com/fox2code/foxloader/dev/FoxLoaderDecompiler.java @@ -0,0 +1,89 @@ +package com.fox2code.foxloader.dev; + +import com.fox2code.foxloader.launcher.utils.SourceUtil; +import org.jetbrains.java.decompiler.main.Fernflower; +import org.jetbrains.java.decompiler.main.decompiler.PrintStreamLogger; +import org.jetbrains.java.decompiler.main.decompiler.SingleFileSaver; +import org.jetbrains.java.decompiler.main.extern.IBytecodeProvider; +import org.jetbrains.java.decompiler.main.extern.IResultSaver; +import org.jetbrains.java.decompiler.util.InterpreterUtil; +import org.lwjgl.LWJGLUtil; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +public class FoxLoaderDecompiler extends SingleFileSaver implements IBytecodeProvider, IResultSaver { + private static final HashMap options = new HashMap<>(); + + static { + options.put("asc", "1"); + options.put("bsm", "1"); + options.put("sef", "1"); + options.put("jrt", "1"); + options.put("ega", "1"); + options.put("nls", "0"); + options.put("pll", "125"); + options.put("ind", " "); + } + + private final Fernflower engine; + + public FoxLoaderDecompiler(File source, File destination, boolean client) { + super(destination); + engine = new Fernflower(this, this, options, new PrintStreamLogger(System.out)); + engine.addLibrary(SourceUtil.getSourceFile(FoxLoaderDecompiler.class)); + if (client) { + engine.addLibrary(SourceUtil.getSourceFile(LWJGLUtil.class)); + } + engine.addSource(source); + } + + public void decompile() { + engine.decompileContext(); + } + + public String fixUpContent(String className, String content) { + // TODO Hexify color int. + return content; + } + + // ******************************************************************* + // Interface IBytecodeProvider + // ******************************************************************* + + @Override + public byte[] getBytecode(String externalPath, String internalPath) throws IOException { + File file = new File(externalPath); + if (internalPath == null) { + return InterpreterUtil.getBytes(file); + } + else { + try (ZipFile archive = new ZipFile(file)) { + ZipEntry entry = archive.getEntry(internalPath); + if (entry == null) throw new IOException("Entry not found: " + internalPath); + return InterpreterUtil.getBytes(archive, entry); + } + } + } + + // ******************************************************************* + // Interface IResultSaver + // ******************************************************************* + + @Override + public synchronized void saveClassEntry(String path, String archiveName, String qualifiedName, String entryName, String content, int[] mapping) { + if (entryName.endsWith(".java")) { + content = fixUpContent(entryName.substring(0, entryName.length() - 5).replace('/', '.'), content); + } + super.saveClassEntry(path, archiveName, qualifiedName, entryName, content, mapping); + } + + @Override + public void saveClassFile(String path, String qualifiedName, String entryName, String content, int[] mapping) { + super.saveClassFile(path, qualifiedName, entryName, + fixUpContent(qualifiedName.replace('/', '.'), content), mapping); + } +} diff --git a/dev/src/main/groovy/com/fox2code/foxloader/dev/GradlePlugin.groovy b/dev/src/main/groovy/com/fox2code/foxloader/dev/GradlePlugin.groovy index 39d25ef..c8d9367 100644 --- a/dev/src/main/groovy/com/fox2code/foxloader/dev/GradlePlugin.groovy +++ b/dev/src/main/groovy/com/fox2code/foxloader/dev/GradlePlugin.groovy @@ -14,10 +14,12 @@ import org.gradle.api.Task import org.gradle.api.publish.maven.MavenPublication import org.gradle.api.tasks.JavaExec import org.gradle.jvm.tasks.Jar -import org.jetbrains.java.decompiler.main.decompiler.ConsoleDecompiler import javax.swing.JOptionPane import java.nio.charset.StandardCharsets +import java.nio.file.ClosedFileSystemException +import java.nio.file.FileSystem +import java.nio.file.FileSystems import java.nio.file.Files import java.text.Normalizer @@ -202,7 +204,7 @@ class GradlePlugin implements Plugin { runServer.systemProperty("foxloader.inject-mod", mod.getAbsolutePath()) runServer.systemProperty("foxloader.dev-mode", "true") runServer.workingDir = runDir - if (config.addJitPackCIPublish && + if (config.addJitPackCIPublish && project.rootProject == project && config.modVersion != null && !config.modVersion.isEmpty()) { File root = project.rootProject.rootDir File gitConfig = new File(root, ".git" + File.separator + "config") @@ -306,16 +308,37 @@ class GradlePlugin implements Plugin { PreLoader.patchReIndevForDev(jar, jarFox, client) } if (config.decompileSources) { + File unpickedJarFox = new File(foxLoaderCache, + "net/silveros/" + sideName + "/" + versionFox + "/" + + sideName + "-" + versionFox + "-unpicked.jar") File sourcesJarFox = new File(foxLoaderCache, "net/silveros/" + sideName + "/" + versionFox + "/" + sideName + "-" + versionFox + "-sources.jar") if (config.forceReload && sourcesJarFox.exists()) sourcesJarFox.delete() if (!sourcesJarFox.exists()) { - System.out.println("Decompiling patched ReIndev " + logSideName) - ConsoleDecompiler.main(new String[]{ - "-asc=1", "-bsm=1", "-sef=1", "-jrt=1", "-nls=0", "-ind= ", - jarFox.getAbsolutePath(), sourcesJarFox.getAbsolutePath() - }) + closeJarFileSystem(unpickedJarFox) + if (unpickedJarFox.exists()) unpickedJarFox.delete() + System.out.println("Unpicking ReIndev " + logSideName + " references for source") + PreLoader.patchDevReIndevForSource(jarFox, unpickedJarFox) + try { + System.out.println("Decompiling patched ReIndev " + logSideName) + new FoxLoaderDecompiler(unpickedJarFox, sourcesJarFox, client).decompile() + } catch (Throwable throwable) { + try { + closeJarFileSystem(unpickedJarFox) + closeJarFileSystem(sourcesJarFox) + } finally { + unpickedJarFox.delete() + sourcesJarFox.delete() + } + Throwable root = throwable + while (root.getCause() != null) + root = root.getCause() + root.initCause(client ? + UserMessage.FAIL_DECOMPILE_CLIENT : + UserMessage.FAIL_DECOMPILE_SERVER) + throw throwable + } } } if (client) { @@ -329,6 +352,36 @@ class GradlePlugin implements Plugin { } } + static void closeJarFileSystem(File file) { + URI uri = new URI("jar:file", null, file.toURI().getPath(), null) + FileSystem fileSystem = null + try { + fileSystem = FileSystems.getFileSystem(uri) + if (fileSystem != null) fileSystem.close() + } catch (Exception ignored) {} + if (fileSystem != null) { + try { + Files.exists(fileSystem.getPath("META-INF/MANIFEST.MF")) + } catch (ClosedFileSystemException e) { + new Thread("FoxLoader - Termination Thread") { + @Override + void run() { + try { + sleep(500L) + } catch (Exception ignored) {} + System.exit(-1) + } + }.start() + Throwable root = e + while (root.getCause() != null) root = root.getCause() + if (!(root instanceof UserMessage)) { + root.initCause(UserMessage.UNRECOVERABLE_STATE_DECOMPILE) + } + throw e + } + } + } + static void injectPom(File file,String group,String id,String ver) { File parent = file.getParentFile() if (!parent.isDirectory() && !parent.mkdirs()) diff --git a/dev/src/main/groovy/com/fox2code/foxloader/dev/UserMessage.java b/dev/src/main/groovy/com/fox2code/foxloader/dev/UserMessage.java new file mode 100644 index 0000000..10cae09 --- /dev/null +++ b/dev/src/main/groovy/com/fox2code/foxloader/dev/UserMessage.java @@ -0,0 +1,37 @@ +package com.fox2code.foxloader.dev; + +public final class UserMessage extends Throwable { + public static final UserMessage UNRECOVERABLE_STATE_DECOMPILE = new UserMessage("---", + "Daemon was in an unrecoverable state for decompile and was terminated.", + "if after reloading the project the problem persists you can add", + "\"foxloader.decompileSources = false\" to your build.gradle to disable", + "ReIndev source code decompilation entirely"); + public static final UserMessage FAIL_DECOMPILE_CLIENT = new UserMessage(true); + public static final UserMessage FAIL_DECOMPILE_SERVER = new UserMessage(false); + + private UserMessage(boolean client) { + this("---", + "An error happened while trying to decompile ReIndev " + (client ? "client" : "server"), + "FoxLoader development plugin tried to solve the invalid state for you, but", + "if after reloading the project the problem persists you can add", + "\"foxloader.decompileSources = false\" to your build.gradle to disable", + "ReIndev source code decompilation entirely"); + } + + public UserMessage(String... strings) { + super(format(strings), null); + } + + private static String format(String[] strings) { + StringBuilder stringBuilder = new StringBuilder(); + for (String string : strings) { + stringBuilder.append('\n').append(string); + } + return stringBuilder.toString(); + } + + @Override + public synchronized Throwable fillInStackTrace() { + return this; + } +} diff --git a/gradle.properties b/gradle.properties index adc8aaa..d957aab 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,8 +3,8 @@ org.gradle.parallel=true org.gradle.jvmargs=-Xmx1024m -XX:-UseGCOverheadLimit -Dfile.encoding=UTF-8 # FoxLoader properties -foxloader.version=0.2.3 -foxloader.lastReIndevTransformerChanges=0.2.0 +foxloader.version=0.3.0 +foxloader.lastReIndevTransformerChanges=0.3.0 # ReIndev properties reindev.clientUrl=https://cdn.fox2code.com/files/reindev_2.8_patched.jar diff --git a/server/build.gradle b/server/build.gradle index 22c5064..144c18d 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -1,6 +1,6 @@ // Server build.gradle group 'com.fox2code' -version project['reindev.version'] +version project['foxloader.version'] dependencies { api(project(":common"))