diff --git a/build.gradle b/build.gradle index 39aae86..13e905d 100644 --- a/build.gradle +++ b/build.gradle @@ -12,7 +12,7 @@ buildscript { apply plugin: 'com.github.johnrengelman.shadow' apply plugin: 'java' -version = "2.6.3" +version = "2.6.4" group = "ofdev" // http://maven.apache.org/guides/mini/guide-naming-conventions.html archivesBaseName = "aa_do_not_rename_OptiFineDevTweaker" diff --git a/src/main/java/ofdev/common/FG3.java b/src/main/java/ofdev/common/FG3.java deleted file mode 100644 index 9aac54e..0000000 --- a/src/main/java/ofdev/common/FG3.java +++ /dev/null @@ -1,18 +0,0 @@ -package ofdev.common; - -import java.nio.file.Path; -import java.nio.file.Paths; - -public class FG3 { - public static Path findObfMcJar(String mcVersion, boolean isClient) { - String requestedJar = System.getProperty("ofdev.mcjar"); - if (requestedJar != null) { - return Paths.get(requestedJar); - } - - // because MC_VERSION has invalid value in forge 1.12.2 2855, we can't use MC_VERSION generally - //String mcVersion = System.getenv("MC_VERSION"); - String dist = isClient ? "client" : "server"; - return Utils.gradleHome().resolve("caches/forge_gradle/minecraft_repo/versions").resolve(mcVersion).resolve(dist + ".jar"); - } -} diff --git a/src/main/java/ofdev/common/Utils.java b/src/main/java/ofdev/common/Utils.java index 1494095..bda5e37 100644 --- a/src/main/java/ofdev/common/Utils.java +++ b/src/main/java/ofdev/common/Utils.java @@ -1,6 +1,13 @@ package ofdev.common; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.nio.file.FileVisitResult; import java.nio.file.FileVisitor; import java.nio.file.Files; @@ -8,8 +15,90 @@ import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import java.nio.file.attribute.BasicFileAttributes; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; public class Utils { + public static final Logger LOGGER = LogManager.getLogger("OptiFineDevTweaker"); + + public static String mcVersion() { + // environment variable from new FG? + String envVersion = System.getenv("MC_VERSION"); + if (envVersion != null) { + LOGGER.info("Found Minecraft version {} from environment variable MC_VERSION", envVersion); + return envVersion; + } + Throwable ex1; + // try 1.13+ FMLLoader + try { + Class FMLLoader = Class.forName("net.minecraftforge.fml.loading.FMLLoader"); + Class VersionInfo = Class.forName("net.minecraftforge.fml.loading.VersionInfo"); + Object versionInfo = FMLLoader.getMethod("versionInfo").invoke(null); + String mcVersion = (String) VersionInfo.getMethod("mcVersion").invoke(versionInfo); + LOGGER.info("Found Minecraft version {} from 1.13+ FMLLoader VersionInfo", mcVersion); + return mcVersion; + } catch (ReflectiveOperationException e) { + ex1 = e; + } + Throwable ex2; + // 1.8 - 1.12.2 Loader MC_VERSION - this FML class so can be loaded early + try { + String mcVersion = getFieldValue(Class.forName("net.minecraftforge.fml.common.Loader"), null, "MC_VERSION"); + LOGGER.info("Found Minecraft version {} from 1.8-1.12.2 FML Loader.MC_VERSION", mcVersion); + return mcVersion; + } catch (ClassNotFoundException e) { + ex2 = e; + } + Throwable ex3; + // 1.7.10 - different FML package + try { + String mcVersion = getFieldValue(Class.forName("cpw.mods.fml.common.Loader"), null, "MC_VERSION"); + LOGGER.info("Found Minecraft version {} from 1.7.10 FML Loader.MC_VERSION", mcVersion); + return mcVersion; + } catch (ClassNotFoundException e) { + ex3 = e; + } + RuntimeException error = new IllegalStateException("Could not find Minecraft version!"); + error.addSuppressed(ex1); + error.addSuppressed(ex2); + error.addSuppressed(ex3); + throw error; + } + + public static Path findMinecraftJar() { + String requestedJar = System.getProperty("ofdev.mcjar"); + if (requestedJar != null) { + Path path = Paths.get(requestedJar); + if (!Files.exists(path)) { + throw new IllegalArgumentException("Provided Minecraft jar path " + requestedJar + " doesn't exist!"); + } + Path absolutePath = path.toAbsolutePath(); + LOGGER.info("Found Minecraft jar {} from ofdev.mcjar property", absolutePath); + return absolutePath; + } + Path gradleHome = Utils.gradleHome(); + if (!Files.exists(gradleHome)) { + throw new IllegalStateException("Gradle home doesn't exist at " + gradleHome.toAbsolutePath()); + } + String mcVersion = mcVersion(); + + // FG1 - FG2 + Path oldFgPath = gradleHome.resolve("caches/minecraft/net/minecraft/minecraft") + .resolve(mcVersion).resolve("minecraft-" + mcVersion + ".jar").toAbsolutePath(); + if (Files.exists(oldFgPath)) { + return oldFgPath; + } + // We don't support running server with OptiFine + // FG3 - FG5 + Path newFgPath = Utils.gradleHome().resolve("caches/forge_gradle/minecraft_repo/versions") + .resolve(mcVersion).resolve("client.jar").toAbsolutePath(); + if (Files.exists(newFgPath)) { + return newFgPath; + } + throw new IllegalStateException("Could not fine Minecraft jar file. Try specifying Minecraft jar location with -Dofdev.mcjar=path\n\t" + + "Attempted locations:\n\t" + oldFgPath + "\n\t" + newFgPath); + } public static Path gradleHome() { String gradleHome = System.getenv("GRADLE_USER_HOME"); @@ -46,6 +135,14 @@ public static void rm(Path path) throws IOException { } } } + + private static void mkdirs(Path location) throws IOException { + if (!Files.exists(location)) { + mkdirs(location.getParent()); + Files.createDirectory(location); + } + } + public static void dumpBytecode(Path loc, String className, byte[] code) throws IOException { String subPath = className.replaceAll("\\.", "/") + ".class"; Path location = loc.resolve(subPath); @@ -53,10 +150,135 @@ public static void dumpBytecode(Path loc, String className, byte[] code) throws Files.write(location, code, StandardOpenOption.CREATE); } - private static void mkdirs(Path location) throws IOException { - if (!Files.exists(location)) { - mkdirs(location.getParent()); - Files.createDirectory(location); + // reflection + + @SuppressWarnings("unchecked") public static T getFieldValue(Class clazz, C obj, String fieldName) { + try { + Field field = clazz.getDeclaredField(fieldName); + field.setAccessible(true); + return (T) field.get(obj); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new RuntimeException(e); } } + + @SuppressWarnings("unchecked") public static T invokeMethod(Class clazz, C obj, String methodName, Object... args) { + try { + Method method = findMethod(clazz, methodName, args); + method.setAccessible(true); + return (T) method.invoke(obj, args); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + public static Method findMethod(Class cl, String name, Object... argValues) { + String toFindString = name + "(" + + Arrays.stream(argValues).map(x -> x.getClass().toString()).reduce((a, b) -> a + ", " + b).orElse("") + ")"; + + Method found = null; + StringBuilder errorString = null; + searching: + for (Method m : getAllMethods(cl)) { + if (!m.getName().equals(name)) { + continue; + } + if (m.getParameterCount() != argValues.length) { + continue; + } + Class[] argTypes = m.getParameterTypes(); + for (int i = 0; i < argValues.length; i++) { + if (!argTypes[i].isAssignableFrom(argValues[i].getClass())) { + continue searching; + } + } + if (found != null) { + if (errorString == null) { + String candidateArgs = Arrays.stream(found.getParameterTypes()).map(Object::toString).reduce((a, b) -> a + ", " + b).orElse(""); + errorString = new StringBuilder("Ambiguous method for specified name and types: " + toFindString + ", found candidate methods\n" + + found.getReturnType() + " " + name + "(" + candidateArgs + ")\n"); + } + String candidateArgs = Arrays.stream(m.getParameterTypes()).map(Object::toString).reduce((a, b) -> a + ", " + b).orElse(""); + errorString.append(m.getReturnType()).append(" ").append(name).append("(").append(candidateArgs).append(")\n"); + } + found = m; + } + if (errorString != null) { + throw new RuntimeException(errorString.toString()); + } + + if (found == null) { + throw new RuntimeException(new NoSuchMethodException(toFindString)); + } + return found; + } + + private static Set getAllMethods(Class cl) { + Set methods = new HashSet<>(Arrays.asList(cl.getDeclaredMethods())); + if (cl.getSuperclass() != null) { + methods.addAll(getAllMethods(cl.getSuperclass())); + } + for (Class i : cl.getInterfaces()) { + methods.addAll(getAllMethods(i)); + } + return methods; + } + + public static void setFieldValue(Class clazz, String fieldName, Object instance, Object newObject) { + try { + Field field = clazz.getDeclaredField(fieldName); + field.setAccessible(true); + field.set(instance, newObject); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + @SuppressWarnings("unchecked") public static T construct(Class clazz, Object... args) { + try { + Constructor constr = findConstructor(clazz, args); + return (T) constr.newInstance(args); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + public static Constructor findConstructor(Class cl, Object... argValues) { + String toFindString = "(" + + Arrays.stream(argValues).map(x -> x.getClass().toString()).reduce((a, b) -> a + ", " + b).orElse("") + ")"; + + Constructor found = null; + StringBuilder errorString = null; + searching: + for (Constructor c : cl.getDeclaredConstructors()) { + if (c.getParameterCount() != argValues.length) { + continue; + } + Class[] argTypes = c.getParameterTypes(); + for (int i = 0; i < argValues.length; i++) { + if (!argTypes[i].isAssignableFrom(argValues[i].getClass())) { + continue searching; + } + } + if (found != null) { + if (errorString == null) { + String candidateArgs = Arrays.stream(found.getParameterTypes()).map(Object::toString).reduce((a, b) -> a + ", " + b).orElse(""); + errorString = new StringBuilder("Ambiguous constructor for specified arg types: " + toFindString + + ", found candidate constructors\n(" + candidateArgs + ")\n"); + } + String candidateArgs = Arrays.stream(c.getParameterTypes()).map(Object::toString).reduce((a, b) -> a + ", " + b).orElse(""); + errorString.append("(").append(candidateArgs).append(")\n"); + } + found = c; + } + if (errorString != null) { + throw new RuntimeException(errorString.toString()); + } + + if (found == null) { + throw new RuntimeException(new NoSuchMethodException(toFindString)); + } + return found; + } + } diff --git a/src/main/java/ofdev/launchwrapper/OptifineDevAdapter.java b/src/main/java/ofdev/launchwrapper/OptifineDevAdapter.java index 6f3849f..06772d6 100644 --- a/src/main/java/ofdev/launchwrapper/OptifineDevAdapter.java +++ b/src/main/java/ofdev/launchwrapper/OptifineDevAdapter.java @@ -14,6 +14,7 @@ import java.util.List; // this class is a modified version of FMLRemappingAdapter +@SuppressWarnings("deprecation") public class OptifineDevAdapter extends RemappingClassAdapter { public OptifineDevAdapter(ClassVisitor cv) { @@ -38,16 +39,13 @@ public void visit(int version, int access, String name, String signature, String } String notchName = OptifineDevRemapper.NOTCH_MCP.notchFromMcpOrDefault(name); String notchSuperName = OptifineDevRemapper.NOTCH_MCP.notchFromMcpOrDefault(superName); - String[] notchInterfaces = Arrays.asList(interfaces).stream().map(OptifineDevRemapper.NOTCH_MCP::notchFromMcpOrDefault).toArray(String[]::new); + String[] notchInterfaces = Arrays.stream(interfaces).map(OptifineDevRemapper.NOTCH_MCP::notchFromMcpOrDefault).toArray(String[]::new); OptifineDevRemapper.NOTCH_MCP.mergeSuperMaps(notchName, notchSuperName, notchInterfaces); super.visit(version, access, name, signature, superName, interfaces); } @Override public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { - if(this.className.equals("rl")) { - int i = 0; - } OptifineDevRemapper remapper = OptifineDevRemapper.NOTCH_MCP; FieldVisitor fv = cv.visitField(access, remapper.mapMemberFieldName(className, name, desc), diff --git a/src/main/java/ofdev/launchwrapper/OptifineDevRemapper.java b/src/main/java/ofdev/launchwrapper/OptifineDevRemapper.java index 0bef63e..d46b5cd 100644 --- a/src/main/java/ofdev/launchwrapper/OptifineDevRemapper.java +++ b/src/main/java/ofdev/launchwrapper/OptifineDevRemapper.java @@ -1,8 +1,11 @@ package ofdev.launchwrapper; +import static ofdev.common.Utils.LOGGER; + import LZMA.LzmaInputStream; import net.minecraft.launchwrapper.Launch; import net.minecraft.launchwrapper.LaunchClassLoader; +import ofdev.common.Utils; import org.objectweb.asm.ClassReader; import org.objectweb.asm.commons.Remapper; import org.objectweb.asm.tree.ClassNode; @@ -29,7 +32,6 @@ // this class is a modified version of FMLDeobfuscatingRemapper public class OptifineDevRemapper extends Remapper { - private static final MethodHandle getPatchedResource; public static final OptifineDevRemapper NOTCH_MCP; static { @@ -37,15 +39,17 @@ public class OptifineDevRemapper extends Remapper { Class cpm; try { cpm = Class.forName("net.minecraftforge.fml.common.patcher.ClassPatchManager"); + LOGGER.info("Found ClassPatchManager in 1.8-1.12.2 package"); } catch (ClassNotFoundException ex) { cpm = Class.forName("cpw.mods.fml.common.patcher.ClassPatchManager"); // 1.7.10 + LOGGER.info("Found ClassPatchManager in 1.7.10 package"); } Object classPathManager = cpm.getField("INSTANCE").get(null); Method m = cpm.getMethod("getPatchedResource", String.class, String.class, LaunchClassLoader.class); getPatchedResource = MethodHandles.lookup().unreflect(m).bindTo(classPathManager); } catch (Throwable t) { - throw new RuntimeException(t); + throw new RuntimeException("Unable to fine ClassPatchManager.getPatchedResource method", t); } String notch2mcpProp = System.getProperty("net.minecraftforge.gradle.GradleStart.srg.notch-mcp"); if (notch2mcpProp != null) { @@ -54,11 +58,10 @@ public class OptifineDevRemapper extends Remapper { String srg2mcp = System.getProperty("net.minecraftforge.gradle.GradleStart.srg.srg-mcp"); if (srg2mcp == null) throw new IllegalStateException("Current version of ForgeGradle is not supported! Please report us!"); - NOTCH_MCP = new OptifineDevRemapper(UtilsLW.mcVersion(), srg2mcp); + NOTCH_MCP = new OptifineDevRemapper(Utils.mcVersion(), srg2mcp); } } - private Map classNameMap, classNameMapInverse; private Map> rawFieldMaps; @@ -206,10 +209,6 @@ public void parseSrg( } } - public boolean isRemappedClass(String className) { - return !map(className).equals(className); - } - // not static for getFieldType private void parseField(String[] parts, Map> rawFieldMaps, boolean withSignatureKey) { String oldSrg = parts[1]; @@ -241,8 +240,8 @@ private void parseField(String[] parts, Map> rawFiel private final Map> fieldDescriptions = new HashMap<>(); // Cache null values so we don't waste time trying to recompute classes with no field or method maps - private Set negativeCacheMethods = new HashSet<>(); - private Set negativeCacheFields = new HashSet<>(); + private final Set negativeCacheMethods = new HashSet<>(); + private final Set negativeCacheFields = new HashSet<>(); private String getFieldType(String owner, String name) { if (fieldDescriptions.containsKey(owner)) { @@ -428,7 +427,7 @@ private void findAndMergeSuperMaps(String name) { } String notchName = OptifineDevRemapper.NOTCH_MCP.notchFromMcpOrDefault(name); String notchSuperName = OptifineDevRemapper.NOTCH_MCP.notchFromMcpOrDefault(superName); - String[] notchInterfaces = Arrays.asList(interfaces).stream().map(OptifineDevRemapper.NOTCH_MCP::notchFromMcpOrDefault).toArray(String[]::new); + String[] notchInterfaces = Arrays.stream(interfaces).map(OptifineDevRemapper.NOTCH_MCP::notchFromMcpOrDefault).toArray(String[]::new); mergeSuperMaps(notchName, notchSuperName, notchInterfaces); } catch (IOException e) { e.printStackTrace(); @@ -478,10 +477,6 @@ public void mergeSuperMaps(String name, String superName, String[] interfaces) { // System.out.printf("Maps: %s %s\n", name, methodMap); } - public Set getObfedClasses() { - return new HashSet<>(classNameMap.keySet()); - } - public String notchFromMcp(String className) { return classNameMapInverse.get(className); } @@ -490,7 +485,7 @@ public String notchFromMcpOrDefault(String className) { return classNameMapInverse.getOrDefault(className, className); } - public String getStaticFieldType(String oldType, String oldName, String newType, String newName) { + @SuppressWarnings("unused") public String getStaticFieldType(String oldType, String oldName, String newType, String newName) { String fType = getFieldType(newType, newName); if (oldType.equals(newType)) { return fType; diff --git a/src/main/java/ofdev/launchwrapper/OptifineDevTransformerWrapper.java b/src/main/java/ofdev/launchwrapper/OptifineDevTransformerWrapper.java index 47afe2d..578c92d 100644 --- a/src/main/java/ofdev/launchwrapper/OptifineDevTransformerWrapper.java +++ b/src/main/java/ofdev/launchwrapper/OptifineDevTransformerWrapper.java @@ -11,7 +11,6 @@ import net.minecraft.launchwrapper.IClassTransformer; import net.minecraft.launchwrapper.Launch; -import ofdev.common.FG3; import ofdev.common.Utils; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassWriter; @@ -27,51 +26,28 @@ import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.MethodNode; -import java.io.File; import java.io.IOException; import java.io.UncheckedIOException; import java.lang.reflect.Modifier; -import java.net.JarURLConnection; -import java.net.URISyntaxException; -import java.net.URL; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; -import java.util.zip.ZipFile; // this is needed only in dev environment to get deobfuscated version of OptiFine running public class OptifineDevTransformerWrapper implements IClassTransformer { - // TODO: will it work on windows? - private static final Path MC_JAR; - - static { - String userJarValue = System.getProperty("ofdev.mcjar"); - if (userJarValue != null) { - MC_JAR = Paths.get(userJarValue); - } else if (System.getProperty("net.minecraftforge.gradle.GradleStart.srg.notch-mcp") != null) { - // then using ForgeGradle 2.x or earlier. - MC_JAR = Utils.gradleHome().resolve("caches/minecraft/net/minecraft/minecraft") - .resolve(UtilsLW.mcVersion()).resolve("minecraft-" + UtilsLW.mcVersion() + ".jar"); - } else { - // then using ForgeGradle 3.x or later. - boolean isClient = System.getenv("assetIndex") != null; - MC_JAR = FG3.findObfMcJar(UtilsLW.mcVersion(), isClient); - } - } - - private static final FileSystem mcJar; + private static final Path MC_JAR = Utils.findMinecraftJar(); + private static final FileSystem mcJarFs; static { try { - mcJar = FileSystems.newFileSystem(MC_JAR, Launch.classLoader); + mcJarFs = FileSystems.newFileSystem(MC_JAR, Launch.classLoader); Launch.classLoader.addURL(MC_JAR.toUri().toURL()); } catch (IOException e) { throw new UncheckedIOException(e); @@ -102,7 +78,7 @@ public class OptifineDevTransformerWrapper implements IClassTransformer { String notchName = remapper.notchFromMcp(classJvmName); byte[] vanillaCode = extractVanillaBytecode(basicClass, notchName); - Mutable isModified = new Mutable(false); + Mutable isModified = new Mutable<>(false); byte[] ofTransformedCode = getOptifineTransformedBytecode(name, basicClass, notchName, vanillaCode, isModified); // deobfuscate OptiFine transformed code to MCP names @@ -277,7 +253,7 @@ private Set reconstructAccessTransformers(ClassNode vanilla, Class private ClassNode toDeobfClassNode(byte[] code) { ClassReader classReader = new ClassReader(code); ClassNode transformedNode = new ClassNode(Opcodes.ASM5); - RemappingClassAdapter remapAdapter = new OptifineDevAdapter(transformedNode); + @SuppressWarnings("deprecation") RemappingClassAdapter remapAdapter = new OptifineDevAdapter(transformedNode); // 1.7.10 has a name conflict with superclass and the field shadows parent class field // but optifine uses the parent class field to set it's own @@ -308,14 +284,13 @@ private byte[] getOptifineTransformedBytecode(String name, byte[] basicClass, St } private byte[] extractVanillaBytecode(byte[] basicClass, String notchName) throws IOException { - byte[] vanillaCode = basicClass; if (notchName != null) { - Path classPath = mcJar.getPath(notchName.replace(".", "/") + ".class"); + Path classPath = mcJarFs.getPath(notchName.replace(".", "/") + ".class"); if (Files.exists(classPath)) { return Files.readAllBytes(classPath); } } - return vanillaCode; + return basicClass; } private static ClassNode getClassNode(byte[] data) { @@ -410,26 +385,6 @@ private enum Target { private enum Visibility { PRIVATE, DEFAULT, PROTECTED, PUBLIC; - public int set(int mod) { - // remove existing modifiers - mod &= ~(Modifier.PRIVATE | Modifier.PUBLIC | Modifier.PROTECTED); - // apply new modifier - switch (this) { - case DEFAULT: - break; - case PRIVATE: - mod |= Modifier.PRIVATE; - break; - case PROTECTED: - mod |= Modifier.PROTECTED; - break; - case PUBLIC: - mod |= Modifier.PUBLIC; - break; - } - return mod; - } - public int asInt() { switch (this) { case PUBLIC: diff --git a/src/main/java/ofdev/launchwrapper/OptifineDevTweakerWrapper.java b/src/main/java/ofdev/launchwrapper/OptifineDevTweakerWrapper.java index 9198577..cc9fdbd 100644 --- a/src/main/java/ofdev/launchwrapper/OptifineDevTweakerWrapper.java +++ b/src/main/java/ofdev/launchwrapper/OptifineDevTweakerWrapper.java @@ -38,6 +38,7 @@ public class OptifineDevTweakerWrapper implements ITweaker { OF_TRANSFORMER_LAUNCH_CLASSLOADER = UtilsLW.loadClassLW("optifine.OptiFineClassTransformer"); } + @SuppressWarnings("unused") public static class OptiFineTransformerTransformer implements IClassTransformer { @Override public byte[] transform(String name, String transformedName, byte[] basicClass) { if (name != null && name.equals("optifine.OptiFineClassTransformer")) { @@ -70,27 +71,28 @@ public static class OptiFineTransformerTransformer implements IClassTransformer } } - public static void initOptiTransformer(Object ofTransformer) { + // called from ASM + @SuppressWarnings("unused") public static void initOptiTransformer(Object ofTransformer) { OptifineDevTransformerWrapper.ofTransformer = (IClassTransformer) ofTransformer; try { - Class ofTransformerClass = + @SuppressWarnings("unchecked") Class ofTransformerClass = (Class) OptifineDevTweakerWrapper.OF_TRANSFORMER_LAUNCH_CLASSLOADER; URL ofUrl = ofTransformer.getClass().getProtectionDomain().getCodeSource().getLocation(); JarURLConnection connection = (JarURLConnection) ofUrl.openConnection(); ZipFile file = new ZipFile(new File(connection.getJarFileURL().toURI())); - UtilsLW.setFieldValue(ofTransformerClass, "ofZipFile", ofTransformer, file); + Utils.setFieldValue(ofTransformerClass, "ofZipFile", ofTransformer, file); Class ofPatcher = Launch.classLoader.findClass("optifine.Patcher"); - Object patchMapVal = UtilsLW.invokeMethod(ofPatcher, null, "getConfigurationMap", file); - Object patternsVal = UtilsLW.invokeMethod(ofPatcher, null, "getConfigurationPatterns", patchMapVal); + Object patchMapVal = Utils.invokeMethod(ofPatcher, null, "getConfigurationMap", file); + Object patternsVal = Utils.invokeMethod(ofPatcher, null, "getConfigurationPatterns", patchMapVal); - UtilsLW.setFieldValue(ofTransformerClass, "patchMap", ofTransformer, patchMapVal); - UtilsLW.setFieldValue(ofTransformerClass, "patterns", ofTransformer, patternsVal); + Utils.setFieldValue(ofTransformerClass, "patchMap", ofTransformer, patchMapVal); + Utils.setFieldValue(ofTransformerClass, "patterns", ofTransformer, patternsVal); //System.out.println("Ignore the above, OptiFine should run anyway"); - UtilsLW.setFieldValue(ofTransformer.getClass(), "instance", null, ofTransformer); + Utils.setFieldValue(ofTransformer.getClass(), "instance", null, ofTransformer); } catch (IOException | URISyntaxException | ClassNotFoundException e) { throw new RuntimeException(e); @@ -121,14 +123,14 @@ public static void initOptiTransformer(Object ofTransformer) { // force remove optifine's transformers // getLaunchArguments is called after all tweakers are initialized, so OF transformer should already exist List transformers = - UtilsLW.getFieldValue(LaunchClassLoader.class, Launch.classLoader, "transformers"); + Utils.getFieldValue(LaunchClassLoader.class, Launch.classLoader, "transformers"); transformers.removeIf(t -> t.getClass().getName().equals("optifine.OptiFineClassTransformer")); // also remove all the new exclusions Set classLoaderExceptions = - UtilsLW.getFieldValue(LaunchClassLoader.class, Launch.classLoader, "classLoaderExceptions"); + Utils.getFieldValue(LaunchClassLoader.class, Launch.classLoader, "classLoaderExceptions"); classLoaderExceptions.removeIf(t -> t.startsWith("optifine")); Set transformerExceptions = - UtilsLW.getFieldValue(LaunchClassLoader.class, Launch.classLoader, "transformerExceptions"); + Utils.getFieldValue(LaunchClassLoader.class, Launch.classLoader, "transformerExceptions"); transformerExceptions.removeIf(t -> t.startsWith("optifine")); return new String[0]; } diff --git a/src/main/java/ofdev/launchwrapper/SrgMappings.java b/src/main/java/ofdev/launchwrapper/SrgMappings.java index eb64bec..57dbd36 100644 --- a/src/main/java/ofdev/launchwrapper/SrgMappings.java +++ b/src/main/java/ofdev/launchwrapper/SrgMappings.java @@ -3,11 +3,7 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.UncheckedIOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Paths; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.Scanner; @@ -47,23 +43,11 @@ private static void parseLine(String line) { private static void parseMethod(String substring) { String[] s = substring.split(" "); - final int SRG_NAME = 0/*, SRG_DESC = 1*/, MCP_NAME = 2/*, MCP_DESC = 3*/; - int lastIndex = s[SRG_NAME].lastIndexOf('/') + 1; - if (lastIndex < 0) { - lastIndex = 0; - } - s[SRG_NAME] = s[SRG_NAME].substring(lastIndex); - lastIndex = s[MCP_NAME].lastIndexOf("/") + 1; - if (lastIndex < 0) { - lastIndex = 0; - } - s[MCP_NAME] = s[MCP_NAME].substring(lastIndex); - srgToMcp.put(s[SRG_NAME], s[MCP_NAME]); } @@ -73,21 +57,10 @@ private static void parseField(String str) { } String[] s = str.split(" "); assert s.length == 2; - int lastIndex = s[0].lastIndexOf('/') + 1; - if (lastIndex < 0) { - lastIndex = 0; - } - s[0] = s[0].substring(lastIndex); - lastIndex = s[1].lastIndexOf("/") + 1; - if (lastIndex < 0) { - lastIndex = 0; - } - s[1] = s[1].substring(lastIndex); - srgToMcp.put(s[0], s[1]); } } diff --git a/src/main/java/ofdev/launchwrapper/UtilsLW.java b/src/main/java/ofdev/launchwrapper/UtilsLW.java index 98e0ed9..814cec5 100644 --- a/src/main/java/ofdev/launchwrapper/UtilsLW.java +++ b/src/main/java/ofdev/launchwrapper/UtilsLW.java @@ -1,29 +1,10 @@ package ofdev.launchwrapper; import net.minecraft.launchwrapper.Launch; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; +import ofdev.common.Utils; public class UtilsLW { - public static final String mcVersion() { - // because javac authors decided to do an optimization for this one specialcase thing of compiletime expressions - // we need to reflectively access a *public static final* field just so that it doesn't get inlined at compiletime - // and so this can be MC-version independent - try { - return getFieldValue(loadClassLW("net.minecraftforge.common.ForgeVersion"), null, "mcVersion"); - } catch (RuntimeException ex) { - // 1.7.10 doesn't have it in ForgeVersion - return getFieldValue(loadClassLW("cpw.mods.fml.common.Loader"), null, "MC_VERSION"); - } - } - // Note: all of these reflection methods are expected to be very slow, and not used too frequently public static Class loadClassLW(String name) { try { @@ -38,137 +19,8 @@ public static Class loadClassLW(String name) { } } - public static T getFieldValue(Class clazz, C obj, String fieldName) { - try { - Field field = clazz.getDeclaredField(fieldName); - field.setAccessible(true); - return (T) field.get(obj); - } catch (NoSuchFieldException | IllegalAccessException e) { - throw new RuntimeException(e); - } - } - - public static T invokeMethod(Class clazz, C obj, String methodName, Object... args) { - try { - Method method = findMethod(clazz, methodName, args); - method.setAccessible(true); - return (T) method.invoke(obj, args); - } catch (IllegalAccessException | InvocationTargetException e) { - throw new RuntimeException(e); - } - } - - public static Method findMethod(Class cl, String name, Object... argValues) { - String toFindString = name + "(" + - Arrays.stream(argValues).map(x -> x.getClass().toString()).reduce((a, b) -> a + ", " + b).orElse("") + ")"; - - Method found = null; - StringBuilder errorString = null; - searching: - for (Method m : getAllMethods(cl)) { - if (!m.getName().equals(name)) { - continue; - } - if (m.getParameterCount() != argValues.length) { - continue; - } - Class[] argTypes = m.getParameterTypes(); - for (int i = 0; i < argValues.length; i++) { - if (!argTypes[i].isAssignableFrom(argValues[i].getClass())) { - continue searching; - } - } - if (found != null) { - if (errorString == null) { - String candidateArgs = Arrays.stream(found.getParameterTypes()).map(Object::toString).reduce((a, b) -> a + ", " + b).orElse(""); - errorString = new StringBuilder("Ambiguous method for specified name and types: " + toFindString + ", found candidate methods\n" + - found.getReturnType() + " " + name + "(" + candidateArgs + ")\n"); - } - String candidateArgs = Arrays.stream(m.getParameterTypes()).map(Object::toString).reduce((a, b) -> a + ", " + b).orElse(""); - errorString.append(m.getReturnType()).append(" ").append(name).append("(").append(candidateArgs).append(")\n"); - } - found = m; - } - if (errorString != null) { - throw new RuntimeException(errorString.toString()); - } - - if (found == null) { - throw new RuntimeException(new NoSuchMethodException(toFindString)); - } - return found; - } - - private static Set getAllMethods(Class cl) { - Set methods = new HashSet<>(Arrays.asList(cl.getDeclaredMethods())); - if (cl.getSuperclass() != null) { - methods.addAll(getAllMethods(cl.getSuperclass())); - } - for (Class i : cl.getInterfaces()) { - methods.addAll(getAllMethods(i)); - } - return methods; - } - - public static void setFieldValue(Class clazz, String fieldName, Object instance, Object newObject) { - try { - Field field = clazz.getDeclaredField(fieldName); - field.setAccessible(true); - field.set(instance, newObject); - } catch (NoSuchFieldException | IllegalAccessException e) { - throw new RuntimeException(e); - } - } - - public static T construct(Class clazz, Object... args) { - try { - Constructor constr = findConstructor(clazz, args); - return (T) constr.newInstance(args); - } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { - throw new RuntimeException(e); - } - } - - public static Constructor findConstructor(Class cl, Object... argValues) { - String toFindString = "(" + - Arrays.stream(argValues).map(x -> x.getClass().toString()).reduce((a, b) -> a + ", " + b).orElse("") + ")"; - - Constructor found = null; - StringBuilder errorString = null; - searching: - for (Constructor c : cl.getDeclaredConstructors()) { - if (c.getParameterCount() != argValues.length) { - continue; - } - Class[] argTypes = c.getParameterTypes(); - for (int i = 0; i < argValues.length; i++) { - if (!argTypes[i].isAssignableFrom(argValues[i].getClass())) { - continue searching; - } - } - if (found != null) { - if (errorString == null) { - String candidateArgs = Arrays.stream(found.getParameterTypes()).map(Object::toString).reduce((a, b) -> a + ", " + b).orElse(""); - errorString = new StringBuilder("Ambiguous constructor for specified arg types: " + toFindString + - ", found candidate constructors\n(" + candidateArgs + ")\n"); - } - String candidateArgs = Arrays.stream(c.getParameterTypes()).map(Object::toString).reduce((a, b) -> a + ", " + b).orElse(""); - errorString.append("(").append(candidateArgs).append(")\n"); - } - found = c; - } - if (errorString != null) { - throw new RuntimeException(errorString.toString()); - } - - if (found == null) { - throw new RuntimeException(new NoSuchMethodException(toFindString)); - } - return found; - } - // invoked from ASMed optifine code - public static void fixReflector() { + @SuppressWarnings("unused") public static void fixReflector() { try { Class reflector = UtilsLW.loadClassLW("Reflector"); @@ -178,26 +30,26 @@ public static void fixReflector() { // 1.7.10 doesn't have this return; } - Object ForgeBlock = UtilsLW.getFieldValue(reflector, null, "ForgeBlock"); - Object ForgeBlock_getLightOpacity = UtilsLW.getFieldValue(reflector, null, "ForgeBlock_getLightOpacity"); - Object ForgeBlock_getLightValue = UtilsLW.getFieldValue(reflector, null, "ForgeBlock_getLightValue"); + Object ForgeBlock = Utils.getFieldValue(reflector, null, "ForgeBlock"); + Object ForgeBlock_getLightOpacity = Utils.getFieldValue(reflector, null, "ForgeBlock_getLightOpacity"); + Object ForgeBlock_getLightValue = Utils.getFieldValue(reflector, null, "ForgeBlock_getLightValue"); Class ReflectorMethod = ForgeBlock_getLightOpacity.getClass(); Class IBlockState = UtilsLW.loadClassLW("net.minecraft.block.state.IBlockState"); Class IBlockAccess = UtilsLW.loadClassLW("net.minecraft.world.IBlockAccess"); Class BlockPos = UtilsLW.loadClassLW("net.minecraft.util.math.BlockPos"); - if (UtilsLW.invokeMethod(ReflectorMethod, ForgeBlock_getLightOpacity, "getTargetMethod") == null) { - Object new_ForgeBlock_getLightOpacity = UtilsLW.construct(ReflectorMethod, ForgeBlock, "getLightOpacity", new Class[]{ + if (Utils.invokeMethod(ReflectorMethod, ForgeBlock_getLightOpacity, "getTargetMethod") == null) { + Object new_ForgeBlock_getLightOpacity = Utils.construct(ReflectorMethod, ForgeBlock, "getLightOpacity", new Class[]{ IBlockState, IBlockAccess, BlockPos }); - UtilsLW.setFieldValue(reflector, "ForgeBlock_getLightOpacity", null, new_ForgeBlock_getLightOpacity); + Utils.setFieldValue(reflector, "ForgeBlock_getLightOpacity", null, new_ForgeBlock_getLightOpacity); } - if (UtilsLW.invokeMethod(ReflectorMethod, ForgeBlock_getLightValue, "getTargetMethod") == null) { - Object new_ForgeBlock_getLightOpacity = UtilsLW.construct(ReflectorMethod, ForgeBlock, "getLightValue", new Class[]{ + if (Utils.invokeMethod(ReflectorMethod, ForgeBlock_getLightValue, "getTargetMethod") == null) { + Object new_ForgeBlock_getLightOpacity = Utils.construct(ReflectorMethod, ForgeBlock, "getLightValue", new Class[]{ IBlockState, IBlockAccess, BlockPos }); - UtilsLW.setFieldValue(reflector, "ForgeBlock_getLightValue", null, new_ForgeBlock_getLightOpacity); + Utils.setFieldValue(reflector, "ForgeBlock_getLightValue", null, new_ForgeBlock_getLightOpacity); } } catch (Throwable t) { t.printStackTrace(); diff --git a/src/main/java/ofdev/modlauncher/OFDevRetransformer.java b/src/main/java/ofdev/modlauncher/OFDevRetransformer.java index 6d1bd18..3466c27 100644 --- a/src/main/java/ofdev/modlauncher/OFDevRetransformer.java +++ b/src/main/java/ofdev/modlauncher/OFDevRetransformer.java @@ -1,5 +1,7 @@ package ofdev.modlauncher; +import static ofdev.common.Utils.LOGGER; + import cpw.mods.modlauncher.Launcher; import cpw.mods.modlauncher.TransformationServiceDecorator; import cpw.mods.modlauncher.api.IEnvironment; @@ -9,8 +11,6 @@ import cpw.mods.modlauncher.api.ITransformerVotingContext; import cpw.mods.modlauncher.api.TransformerVoteResult; import ofdev.common.Utils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.commons.ClassRemapper; import org.objectweb.asm.tree.ClassNode; @@ -25,8 +25,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.List; @@ -37,9 +35,10 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import javax.annotation.Nonnull; + public class OFDevRetransformer implements ITransformer { - private static final Logger LOGGER = LogManager.getLogger(); private final Set targets; private final OfDevRemapper remapper; @@ -75,7 +74,7 @@ private static Collection findOptiFineClasses(IEnvironment env serviceField.setAccessible(true); ITransformationService service = (ITransformationService) serviceField.get(optiFine); Class clazz = service.getClass(); - Method getModule = Class.class.getMethod("getModule"); + @SuppressWarnings("JavaReflectionMemberAccess") Method getModule = Class.class.getMethod("getModule"); Object module = getModule.invoke(clazz); Class Module = Class.forName("java.lang.Module"); Method getLayer = Module.getMethod("getLayer"); @@ -91,7 +90,7 @@ private static Collection findOptiFineClasses(IEnvironment env Object reference = referenceMethod.invoke(optiModule.get()); Method locationMethod = Class.forName("java.lang.module.ModuleReference").getMethod("location"); @SuppressWarnings("unchecked") Optional location = (Optional) locationMethod.invoke(reference); - String path = location.get().getPath(); + String path = location.orElseThrow(() -> new IllegalStateException("No module location!")).getPath(); optifineFile = path.substring(0, path.lastIndexOf('#')); optifineFile = Paths.get(optifineFile).getFileName().toString(); } catch (ReflectiveOperationException ex) { @@ -143,7 +142,7 @@ private static Collection findOptiFineClasses(IEnvironment env return newTargets.stream().map(Target::targetClass).collect(Collectors.toList()); } - @Override public ClassNode transform(ClassNode input, ITransformerVotingContext context) { + @Nonnull @Override public ClassNode transform(@Nonnull ClassNode input, @Nonnull ITransformerVotingContext context) { ClassNode output = new ClassNode(); ClassRemapper classRemapper = new ClassRemapper(output, remapper); input.accept(classRemapper); @@ -157,11 +156,11 @@ private static Collection findOptiFineClasses(IEnvironment env return output; } - @Override public TransformerVoteResult castVote(ITransformerVotingContext context) { + @Nonnull @Override public TransformerVoteResult castVote(@Nonnull ITransformerVotingContext context) { return TransformerVoteResult.YES; } - @Override public Set targets() { + @Nonnull @Override public Set targets() { return targets; } diff --git a/src/main/java/ofdev/modlauncher/OFDevTransformationService.java b/src/main/java/ofdev/modlauncher/OFDevTransformationService.java index 0ea980d..b6ae6e1 100644 --- a/src/main/java/ofdev/modlauncher/OFDevTransformationService.java +++ b/src/main/java/ofdev/modlauncher/OFDevTransformationService.java @@ -14,7 +14,6 @@ import cpw.mods.modlauncher.api.ITransformationService; import cpw.mods.modlauncher.api.ITransformer; import cpw.mods.modlauncher.api.IncompatibleEnvironmentException; -import ofdev.common.FG3; import ofdev.common.Utils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -41,7 +40,6 @@ import java.util.Collection; import java.util.Collections; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -49,43 +47,23 @@ import java.util.function.BiFunction; import java.util.zip.ZipFile; +import javax.annotation.Nonnull; + public class OFDevTransformationService implements ITransformationService { private static final Logger LOGGER = LogManager.getLogger(); private static Path mcJar; public static Path CLASS_DUMP_LOCATION; - private static Path findObfMcJar(IEnvironment env) { - String requestedJar = System.getProperty("ofdev.mcjar"); - if (requestedJar != null) { - return Paths.get(requestedJar); - } - - String mcVersion = System.getenv("MC_VERSION"); - // newer forge/FG versions (mc 1.18+?) don't have MC_VERSION, instead supply mc version as --fml.mcVersion only - if (mcVersion == null) { - try { - Class FMLLoader = Class.forName("net.minecraftforge.fml.loading.FMLLoader"); - Class VersionInfo = Class.forName("net.minecraftforge.fml.loading.VersionInfo"); - Object versionInfo = FMLLoader.getMethod("versionInfo").invoke(null); - mcVersion = (String) VersionInfo.getMethod("mcVersion").invoke(versionInfo); - } catch (ReflectiveOperationException ex) { - throw new IllegalStateException(ex); - } - } - String target = env.getProperty(IEnvironment.Keys.LAUNCHTARGET.get()).get().toLowerCase(Locale.ROOT);//target=fmluserdevclient for client - return FG3.findObfMcJar(mcVersion, target.contains("client") || target.contains("data")); - } - private static IEnvironment env; private static BiConsumer fixMemberAccess; - @Override public String name() { + @Nonnull @Override public String name() { return "OptiFineDevTransformationService"; } - @Override public void initialize(IEnvironment environment) { - mcJar = findObfMcJar(environment); + @Override public void initialize(@Nonnull IEnvironment environment) { + mcJar = Utils.findMinecraftJar(); try { Path classDump = Paths.get(".").toAbsolutePath().normalize().resolve(".optifineDev.classes"); Utils.rm(classDump); @@ -95,11 +73,11 @@ private static Path findObfMcJar(IEnvironment env) { } } - @Override public void beginScanning(IEnvironment environment) { + @Override public void beginScanning(@Nonnull IEnvironment environment) { } - @Override public void onLoad(IEnvironment envIn, Set otherServices) throws IncompatibleEnvironmentException { + @Override public void onLoad(@Nonnull IEnvironment envIn, @Nonnull Set otherServices) throws IncompatibleEnvironmentException { env = envIn; if (!otherServices.contains("OptiFine")) { throw new IncompatibleEnvironmentException("Couldn't find OptiFine!"); @@ -178,7 +156,7 @@ private static Path findObfMcJar(IEnvironment env) { } // called from asm-generated code - public static InputStream getResourceStream(String path) { + @SuppressWarnings("unused") public static InputStream getResourceStream(String path) { try { if (!path.startsWith("/")) { path = '/' + path; @@ -191,7 +169,7 @@ public static InputStream getResourceStream(String path) { } } - public static ClassNode wrapOptiFineTransform(ClassNode transformed, ClassNode original) { + @SuppressWarnings("unused") public static ClassNode wrapOptiFineTransform(ClassNode transformed, ClassNode original) { ClassNode output = new ClassNode(); Optional> srgtomcp = env.findNameMapping("srg"); if (!srgtomcp.isPresent()) { @@ -212,7 +190,7 @@ public static ClassNode wrapOptiFineTransform(ClassNode transformed, ClassNode o return output; } - @Override public List transformers() { + @SuppressWarnings("rawtypes") @Nonnull @Override public List transformers() { return Collections.singletonList(new OFDevRetransformer(env)); } diff --git a/src/main/java/ofdev/modlauncher/OfDevRemapper.java b/src/main/java/ofdev/modlauncher/OfDevRemapper.java index 07cf25a..e343b64 100644 --- a/src/main/java/ofdev/modlauncher/OfDevRemapper.java +++ b/src/main/java/ofdev/modlauncher/OfDevRemapper.java @@ -17,7 +17,7 @@ public class OfDevRemapper extends Remapper { return srg2mcp.apply(INameMappingService.Domain.METHOD, name); } - /*@Override*/ public String mapRecordComponentName(String owner, String name, String descriptor) { + /*@Override*/ @SuppressWarnings("unused") public String mapRecordComponentName(String owner, String name, String descriptor) { return srg2mcp.apply(INameMappingService.Domain.METHOD, name); }