Thanks, for some reason, that section isn't showing up in OP for me. I tried the jar method with no avail, I'll see if I can get the launch argument to work.
Okay, so both methods are working for me... in the sense that they both fail in the same way. All I get is a nonsensical ClassNotFoundException, when I know for a fact the class exists. Here it is, incase someone knows the solution:
java.lang.RuntimeException: java.lang.ClassNotFoundException: cpw.mods.fml.common.asm.FMLSanityChecker
at cpw.mods.fml.relauncher.CoreModManager.injectTransformers(CoreModManager.java:431)
at cpw.mods.fml.relauncher.FMLLaunchHandler.injectPostfixTransformers(FMLLaunchHandler.java:108)
at cpw.mods.fml.relauncher.FMLLaunchHandler.appendCoreMods(FMLLaunchHandler.java:113)
at cpw.mods.fml.common.launcher.FMLTweaker.injectIntoClassLoader(FMLTweaker.java:61)
at net.minecraft.launchwrapper.Launch.launch(Launch.java:111)
at net.minecraft.launchwrapper.Launch.main(Launch.java:27)
Caused by: java.lang.ClassNotFoundException: cpw.mods.fml.common.asm.FMLSanityChecker
at net.minecraft.launchwrapper.LaunchClassLoader.findClass(LaunchClassLoader.java:179)
at java.lang.ClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Unknown Source)
at cpw.mods.fml.relauncher.CoreModManager.injectTransformers(CoreModManager.java:420)
... 5 more
Caused by: java.lang.NullPointerException
at net.minecraft.launchwrapper.LaunchClassLoader.findClass(LaunchClassLoader.java:171)
... 10 more
My best guess is to manually add dependencies to the mod container. You can either look in Loader.computeDependencies() to see how to add them directly to your dependency list, or look in ModLoaderModContainer.bindMetadata() to see how to override bindMetaData() and call Loader.computeDependencies from there.
If you use "after" instead of "required-after", then it will ignore the dependency entirely if Optifine is not installed. Also, there's always "after:*" which will safe guard that you get your stuff in last, but that's usually a last resort.
Yeah... coremod dependencies are hard dependencies only. Those can be done using @DependsOn. Maybe there is a way to check for Optifine, and if it is installed, use reflection to add an annotation? That's the best I can think of. Everything from here on out is more down hill on the sketchy/hacky side of things.
EDIT: The above works, but it's no walk in the park. Detecting Optifine is difficult, given no solid information on it, through means of source code. Further more, it requires a preload coremod, as the ASM has to be called before the main mod loads. This also requires that the ASM calls be fully manual... maybe a more hacky option is actually better?
Once again, no problem. I did notice the list of coremods, so that's convenient. Dumping the list should make it easy to tell what the name Optifine uses is. Another option is to tag on to someone that has an API that is designed for overhauls, as that API would have more flexibility as to what is allowed and what is not... Manual ASM would not be as bad as I thought it would, given some tricksy event catching.
I'm having an issue injecting a method. This works just fine as intended in eclipse, however when used in an obfuscated environment I get this error;
2013-10-22 19:29:49 [INFO] [STDOUT] INVOKEVIRTUAL opcode is -1 METHOD_INSN type is 15
2013-10-22 19:29:49 [INFO] [STDOUT] Patching Complete!
2013-10-22 19:29:49 [FINE] [ForgeModLoader] Runtime patching class net/minecraft/world/IBlockAccess (input size 367), found 1 patch
2013-10-22 19:29:49 [FINE] [ForgeModLoader] Successfully applied runtime patches for net/minecraft/world/IBlockAccess (new size 743)
2013-10-22 19:29:49 [SEVERE] [ForgeModLoader] Unable to launch
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at net.minecraft.launchwrapper.Launch.launch(Launch.java:131)
at net.minecraft.launchwrapper.Launch.main(Launch.java:27)
Caused by: java.lang.NoClassDefFoundError: net/minecraft/client/Minecraft
at net.minecraft.client.main.Main.main(SourceFile:37)
... 6 more
Caused by: java.lang.ClassNotFoundException: net.minecraft.client.Minecraft
at net.minecraft.launchwrapper.LaunchClassLoader.findClass(LaunchClassLoader.java:186)
at java.lang.ClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
... 7 more
Caused by: java.lang.RuntimeException: java.lang.ClassNotFoundException: avc
at org.objectweb.asm.ClassWriter.getCommonSuperClass(Unknown Source)
at org.objectweb.asm.ClassWriter.a(Unknown Source)
at org.objectweb.asm.Frame.a(Unknown Source)
at org.objectweb.asm.Frame.a(Unknown Source)
at org.objectweb.asm.MethodWriter.visitMaxs(Unknown Source)
at org.objectweb.asm.tree.MethodNode.accept(Unknown Source)
at org.objectweb.asm.tree.MethodNode.accept(Unknown Source)
at org.objectweb.asm.tree.ClassNode.accept(Unknown Source)
at tim.ClassTransformer.patchClassASM(ClassTransformer.java:183)
at tim.ClassTransformer.transform(ClassTransformer.java:27)
at net.minecraft.launchwrapper.LaunchClassLoader.runTransformers(LaunchClassLoader.java:274)
at net.minecraft.launchwrapper.LaunchClassLoader.findClass(LaunchClassLoader.java:172)
... 9 more
This is my code
Ignore the comments those are just leftover from the OP
public class ClassTransformer implements net.minecraft.launchwrapper.IClassTransformer {
@Override
public byte[] transform(String arg0, String arg1, byte[] arg2) {
if (arg0.equals("atv")) {
System.out.println("********* INSIDE OBFUSCATED EXPLOSION TRANSFORMER ABOUT TO PATCH: " + arg0);
return patchClassASM(arg0, arg2, true);
}
if (arg0.equals("net.minecraft.client.Minecraft")) {
System.out.println("********* INSIDE EXPLOSION TRANSFORMER ABOUT TO PATCH: " + arg0);
return patchClassASM(arg0, arg2, false);
}
return arg2;
}
public byte[] patchClassASM(String name, byte[] bytes, boolean obfuscated) {
String targetMethodName = "";
if(obfuscated == true)
targetMethodName ="k";
else
targetMethodName ="runTick";
//set up ASM class manipulation stuff. Consult the ASM docs for details
ClassNode classNode = new ClassNode();
ClassReader classReader = new ClassReader(bytes);
classReader.accept(classNode, 0);
//Now we loop over all of the methods declared inside the Explosion class until we get to the targetMethodName "doExplosionB"
// find method to inject into
@SuppressWarnings("unchecked")
Iterator<MethodNode> methods = classNode.methods.iterator();
while(methods.hasNext())
{
MethodNode m = methods.next();
System.out.println("********* Method Name: "+m.name + " Desc:" + m.desc);
int fdiv_index = -1;
//Check if this is doExplosionB and it's method signature is (Z)V which means that it accepts a boolean (Z) and returns a void (V)
if ((m.name.equals(targetMethodName) && m.desc.equals("()V")))
{
System.out.println("********* Inside target method!");
// find interesting instructions in method, there is a single FDIV instruction we use as target
//System.out.println("m.instructions.size = " + m.instructions.size());
AbstractInsnNode currentNode = null;
AbstractInsnNode targetNode = null;
@SuppressWarnings("unchecked")
Iterator<AbstractInsnNode> iter = m.instructions.iterator();
int index = -1;
//Loop over the instruction set and find the instruction FDIV which does the division of 1/explosionSize
while (iter.hasNext() && index < 700)
{
index++;
currentNode = iter.next();
AbstractInsnNode nextNode = currentNode.getNext();
AbstractInsnNode thirdNode = nextNode.getNext();
System.out.println("********* index : " + index + " currentNode.getOpcode() = " + currentNode.getOpcode() + " Type: " + currentNode.getType());
if (currentNode.getType() == 15 && currentNode.getOpcode()==-1 && nextNode.getType() == 14 && thirdNode.getType() == 2)
{
targetNode = currentNode;
fdiv_index = index;
}
}
AbstractInsnNode addNode = m.instructions.get(fdiv_index);
AbstractInsnNode invVirt = m.instructions.get(fdiv_index );
if(invVirt.getOpcode() == -1)
{
if(invVirt.getType() == 15){
System.out.println("INVOKEVIRTUAL opcode is " + invVirt.getOpcode() + " METHOD_INSN type is " + invVirt.getType() );
}
}
InsnList toInject = new InsnList();
toInject.add(new MethodInsnNode(184, "tim/Base", "onKeyPressed", "()V"));
m.instructions.insertBefore(targetNode, toInject);
System.out.println("Patching Complete!");
break;
}
}
//ASM specific for cleaning up and returning the final bytes for JVM processing.
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
classNode.accept(writer);
return writer.toByteArray();
}
}
Again, API / Pre-Coremod ASM would have the ability to load special coremods whenever in the loading process they feel like. I will look into tweakers and figure out how they work, and see how we would go about making this work...
Err... I hate to be a self-advertiser, but I'm currently working on an API called LightAPI (I'm a creative namer, no?) that alters when coremods are loaded, which coincidentally is what you want. (for reals, you're not the only one that needs this).
That is the question, isn't it? Still haven't figured it out myself, Forge is very revealing of it's ways, yet it's right there in front of us... My best bet on it is that catching an even somewhere will let us do some old fashion ASM before the mods are loaded, but so far I can't find anything.
I have figured out that patching a too big class will cause problems. You'll get an error with index is not right or file isn't ok or something like that
Do you mean patching a class that is too big, or patching a class so that it becomes too big?
Yes, because if he's patching whole class files the problem is most probably caused by wrong usage of the read() method, best described by karyonix.
Fair enough, I was just giving him the benefit of the doubt, and considering the possibility that he hit an actual technical restriction instead of simply making a mistake.
EDIT: The restriction on InputStreams is documented somewhere as well, so I guess technically it's a technical restriction no matter what, but hey. In fact, I bet the restrictions of code block size and stream reads are directly related.
I don't think he made a mistake, he is probably just using cuelgooners class replacing code, which doesn't work for long class files because of the from karyonix stated fact.
That would make sense. cuelgooner should fix that, if he even updates this thread.
BTW, is there a way/reason to just completely replace the class node? I'm almost seeing a possible use for making "templates" that are exact copies of the desired class bar a different name for compiling and a the additions you want, and then changing the name on runtime before ejecting.
package net.lighttechindustries.lightapi.asm; import java.util.Map; import cpw.mods.fml.relauncher.IFMLLoadingPlugin; public class LightAPIPlugin implements IFMLLoadingPlugin { public LightAPIPlugin() { System.out.println("Iniitiating."); } @Override public String[] getLibraryRequestClass() { return null; } @Override public String[] getASMTransformerClass() { return new String[]{"net.lighttechindustries.lightapi.asm.DummyTransformer"}; } @Override public String getModContainerClass() { return "net.lighttechindustries.lightapi.asm.LightAPIModContainer"; } @Override public String getSetupClass() { return null; } @Override public void injectData(Map<String, Object> data) {} }DummyTransformer:
package net.lighttechindustries.lightapi.asm; import net.minecraft.launchwrapper.IClassTransformer; public class DummyTransformer implements IClassTransformer { @Override public byte[] transform(String arg0, String arg1, byte[] arg2) { return null; } }EDIT: BTW, your plant render mod is amazing.
EDIT: The above works, but it's no walk in the park. Detecting Optifine is difficult, given no solid information on it, through means of source code. Further more, it requires a preload coremod, as the ASM has to be called before the main mod loads. This also requires that the ASM calls be fully manual... maybe a more hacky option is actually better?
2013-10-22 19:29:49 [INFO] [STDOUT] Patching Complete!
2013-10-22 19:29:49 [FINE] [ForgeModLoader] Runtime patching class net/minecraft/world/IBlockAccess (input size 367), found 1 patch
2013-10-22 19:29:49 [FINE] [ForgeModLoader] Successfully applied runtime patches for net/minecraft/world/IBlockAccess (new size 743)
2013-10-22 19:29:49 [SEVERE] [ForgeModLoader] Unable to launch
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at net.minecraft.launchwrapper.Launch.launch(Launch.java:131)
at net.minecraft.launchwrapper.Launch.main(Launch.java:27)
Caused by: java.lang.NoClassDefFoundError: net/minecraft/client/Minecraft
at net.minecraft.client.main.Main.main(SourceFile:37)
... 6 more
Caused by: java.lang.ClassNotFoundException: net.minecraft.client.Minecraft
at net.minecraft.launchwrapper.LaunchClassLoader.findClass(LaunchClassLoader.java:186)
at java.lang.ClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
... 7 more
Caused by: java.lang.RuntimeException: java.lang.ClassNotFoundException: avc
at org.objectweb.asm.ClassWriter.getCommonSuperClass(Unknown Source)
at org.objectweb.asm.ClassWriter.a(Unknown Source)
at org.objectweb.asm.Frame.a(Unknown Source)
at org.objectweb.asm.Frame.a(Unknown Source)
at org.objectweb.asm.MethodWriter.visitMaxs(Unknown Source)
at org.objectweb.asm.tree.MethodNode.accept(Unknown Source)
at org.objectweb.asm.tree.MethodNode.accept(Unknown Source)
at org.objectweb.asm.tree.ClassNode.accept(Unknown Source)
at tim.ClassTransformer.patchClassASM(ClassTransformer.java:183)
at tim.ClassTransformer.transform(ClassTransformer.java:27)
at net.minecraft.launchwrapper.LaunchClassLoader.runTransformers(LaunchClassLoader.java:274)
at net.minecraft.launchwrapper.LaunchClassLoader.findClass(LaunchClassLoader.java:172)
... 9 more
This is my code
Ignore the comments those are just leftover from the OP
public class ClassTransformer implements net.minecraft.launchwrapper.IClassTransformer { @Override public byte[] transform(String arg0, String arg1, byte[] arg2) { if (arg0.equals("atv")) { System.out.println("********* INSIDE OBFUSCATED EXPLOSION TRANSFORMER ABOUT TO PATCH: " + arg0); return patchClassASM(arg0, arg2, true); } if (arg0.equals("net.minecraft.client.Minecraft")) { System.out.println("********* INSIDE EXPLOSION TRANSFORMER ABOUT TO PATCH: " + arg0); return patchClassASM(arg0, arg2, false); } return arg2; } public byte[] patchClassASM(String name, byte[] bytes, boolean obfuscated) { String targetMethodName = ""; if(obfuscated == true) targetMethodName ="k"; else targetMethodName ="runTick"; //set up ASM class manipulation stuff. Consult the ASM docs for details ClassNode classNode = new ClassNode(); ClassReader classReader = new ClassReader(bytes); classReader.accept(classNode, 0); //Now we loop over all of the methods declared inside the Explosion class until we get to the targetMethodName "doExplosionB" // find method to inject into @SuppressWarnings("unchecked") Iterator<MethodNode> methods = classNode.methods.iterator(); while(methods.hasNext()) { MethodNode m = methods.next(); System.out.println("********* Method Name: "+m.name + " Desc:" + m.desc); int fdiv_index = -1; //Check if this is doExplosionB and it's method signature is (Z)V which means that it accepts a boolean (Z) and returns a void (V) if ((m.name.equals(targetMethodName) && m.desc.equals("()V"))) { System.out.println("********* Inside target method!"); // find interesting instructions in method, there is a single FDIV instruction we use as target //System.out.println("m.instructions.size = " + m.instructions.size()); AbstractInsnNode currentNode = null; AbstractInsnNode targetNode = null; @SuppressWarnings("unchecked") Iterator<AbstractInsnNode> iter = m.instructions.iterator(); int index = -1; //Loop over the instruction set and find the instruction FDIV which does the division of 1/explosionSize while (iter.hasNext() && index < 700) { index++; currentNode = iter.next(); AbstractInsnNode nextNode = currentNode.getNext(); AbstractInsnNode thirdNode = nextNode.getNext(); System.out.println("********* index : " + index + " currentNode.getOpcode() = " + currentNode.getOpcode() + " Type: " + currentNode.getType()); if (currentNode.getType() == 15 && currentNode.getOpcode()==-1 && nextNode.getType() == 14 && thirdNode.getType() == 2) { targetNode = currentNode; fdiv_index = index; } } AbstractInsnNode addNode = m.instructions.get(fdiv_index); AbstractInsnNode invVirt = m.instructions.get(fdiv_index ); if(invVirt.getOpcode() == -1) { if(invVirt.getType() == 15){ System.out.println("INVOKEVIRTUAL opcode is " + invVirt.getOpcode() + " METHOD_INSN type is " + invVirt.getType() ); } } InsnList toInject = new InsnList(); toInject.add(new MethodInsnNode(184, "tim/Base", "onKeyPressed", "()V")); m.instructions.insertBefore(targetNode, toInject); System.out.println("Patching Complete!"); break; } } //ASM specific for cleaning up and returning the final bytes for JVM processing. ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); classNode.accept(writer); return writer.toByteArray(); } }Thank you sir, this worked.
That is the question, isn't it? Still haven't figured it out myself, Forge is very revealing of it's ways, yet it's right there in front of us... My best bet on it is that catching an even somewhere will let us do some old fashion ASM before the mods are loaded, but so far I can't find anything.
No problem
I will see what options we have then. Using an API allows for more flexibility in how we get it working.
Do you mean patching a class that is too big, or patching a class so that it becomes too big?
Would it matter? It seems like injecting instructions is worse, considering that code blocks have a size limit, even though classes do not.
I'll let you know what kind of a progress I make. For now, I'm looking into how the tweaker system works.
Fair enough, I was just giving him the benefit of the doubt, and considering the possibility that he hit an actual technical restriction instead of simply making a mistake.
EDIT: The restriction on InputStreams is documented somewhere as well, so I guess technically it's a technical restriction no matter what, but hey. In fact, I bet the restrictions of code block size and stream reads are directly related.
That would make sense. cuelgooner should fix that, if he even updates this thread.
BTW, is there a way/reason to just completely replace the class node? I'm almost seeing a possible use for making "templates" that are exact copies of the desired class bar a different name for compiling and a the additions you want, and then changing the name on runtime before ejecting.