Hi, I'm currently modding my 1.6.4 client with forge to set-up custom skins on my server. I searched how to manage this mod, and I found the ASM patching way. I used the code that I will paste, and everything worked out fine but only while running my mod in eclipse's environnement (so only in an un-obfuscated minecraft) . Otherwise when I test with the official launcher, I get a crash with lots of errors. And I don't understand why or how to fix it. Help me ! :'(
Here is the code + the stacktrace of the error :
SkinTransformer.java
package pkg;
import static org.objectweb.asm.Opcodes.ARETURN;
import static org.objectweb.asm.Opcodes.ALOAD;
import static org.objectweb.asm.Opcodes.INVOKESTATIC;
import java.util.Iterator;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.VarInsnNode;
public class SkinClassTransformer implements net.minecraft.launchwrapper.IClassTransformer {
@Override
public byte[] transform(String arg0, String arg1, byte[] arg2) {
if (arg0.equals("beu")) {
return patchClassASM(arg0, arg2, true);
}
if (arg0.equals("net.minecraft.client.entity.AbstractClientPlayer")) {
return patchClassASM(arg0, arg2, false);
}
return arg2;
}
public byte[] patchClassASM(String name, byte[] bytes, boolean obfuscated) {
String targetMethod1Name = "", targetMethod2Name = "";
boolean ok1 = false, ok2 = false;
if(obfuscated == true) {
targetMethod1Name ="d";
targetMethod2Name = "e";
}
else {
targetMethod1Name ="getSkinUrl";
targetMethod2Name = "getCapeUrl";
}
ClassNode classNode = new ClassNode();
ClassReader classReader = new ClassReader(bytes);
classReader.accept(classNode, 0);
Iterator<MethodNode> methods = classNode.methods.iterator();
while(methods.hasNext())
{
MethodNode m = methods.next();
if ((m.name.equals(targetMethod1Name) && m.desc.equals("(Ljava/lang/String;)Ljava/lang/String;")))
{
InsnList toInject = new InsnList();
toInject.add(new VarInsnNode(ALOAD, 0));
toInject.add(new MethodInsnNode(INVOKESTATIC, "pkg/SkinPlayer", "getURLSkinCustom", "(Ljava/lang/String;)Ljava/lang/String;"));
toInject.add(new InsnNode(ARETURN));
m.instructions = toInject;
ok1 = true;
}
else if ((m.name.equals(targetMethod2Name) && m.desc.equals("(Ljava/lang/String;)Ljava/lang/String;")))
{
InsnList toInject = new InsnList();
toInject.add(new VarInsnNode(ALOAD, 0));
toInject.add(new MethodInsnNode(INVOKESTATIC, "pkg/SkinPlayer", "getURLCapeCustom", "(Ljava/lang/String;)Ljava/lang/String;"));
toInject.add(new InsnNode(ARETURN));
m.instructions = toInject;
ok2 = true;
}
if(ok1 && ok2)
break;
}
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
classNode.accept(writer);
return writer.toByteArray();
}
}
SkinPlayer.java
package pkg;
import java.net.HttpURLConnection;
import java.net.URL;
import net.minecraft.util.StringUtils;
public class SkinPlayer {
private static String connexion (String adress, String par0Str){
int code = 404;
try {
URL u = new URL (adress);
HttpURLConnection huc = ( HttpURLConnection ) u.openConnection ();
huc.setRequestMethod ("GET");
huc.connect () ;
code = huc.getResponseCode() ;
if (code == HttpURLConnection.HTTP_OK)
return adress;
else if (code == HttpURLConnection.HTTP_MOVED_TEMP
|| code == HttpURLConnection.HTTP_MOVED_PERM
|| code == HttpURLConnection.HTTP_SEE_OTHER) {
String newUrl = huc.getHeaderField("Location");
huc = (HttpURLConnection) new URL(newUrl).openConnection();
huc.setRequestMethod ("GET");
huc.connect ();
code = huc.getResponseCode();
if (code == HttpURLConnection.HTTP_OK)
return newUrl;
}
}
catch(Exception e){
}
return String.format("official minecraft.net skins url", new Object[] {StringUtils.stripControlCodes(par0Str)});
}
public static String getURLSkinCustom(String par0Str)
{
String adress=String.format("http://www.somesite.com/skin/%s", new Object[] {StringUtils.stripControlCodes(par0Str)});
return connexion(adress, par0Str);
}
public static String getURLCapeCustom(String par0Str)
{
String adress=String.format("http://www.somesite.com/cape/%s", new Object[] {StringUtils.stripControlCodes(par0Str)});
return connexion(adress, par0Str);
}
}
SkinFMLLoadingPlugin.java
package pkg;
import java.io.File;
import java.util.Map;
import cpw.mods.fml.relauncher.IFMLLoadingPlugin.MCVersion;
@MCVersion(value = "1.6.4")
public class SkinFMLLoadingPlugin implements cpw.mods.fml.relauncher.IFMLLoadingPlugin {
@Override
public String[] getLibraryRequestClass() {
return null;
}
@Override
public String[] getASMTransformerClass() {
return new String[]{SkinClassTransformer.class.getName()};
}
@Override
public String getModContainerClass() {
return SkinDummyContainer.class.getName();
}
@Override
public String getSetupClass() {
return null;
}
@Override
public void injectData(Map<String, Object> data) {
}
}
SkinDummyContainer.java
package pkg;
import java.util.Arrays;
import net.minecraftforge.common.Configuration;
import net.minecraftforge.event.ForgeSubscribe;
import com.google.common.eventbus.EventBus;
import cpw.mods.fml.common.DummyModContainer;
import cpw.mods.fml.common.LoadController;
import cpw.mods.fml.common.Mod.EventHandler;
import cpw.mods.fml.common.ModMetadata;
import cpw.mods.fml.common.event.FMLConstructionEvent;
import cpw.mods.fml.common.event.FMLInitializationEvent;
import cpw.mods.fml.common.event.FMLPostInitializationEvent;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
public class SkinDummyContainer extends DummyModContainer {
public SkinDummyContainer() {
super(new ModMetadata());
ModMetadata meta = getMetadata();
meta.modId = "something";
meta.name = "something";
meta.version = "1.0";
meta.credits = "something";
meta.authorList = Arrays.asList("something");
meta.description = "something";
meta.url = "something";
meta.updateUrl = "";
meta.screenshots = new String[0];
meta.logoFile = "";
}
@ForgeSubscribe
public boolean registerBus(EventBus bus, LoadController controller) {
bus.register(this);
return true;
}
@EventHandler
public void modConstruction(FMLConstructionEvent event){
}
@EventHandler
public void init(FMLInitializationEvent evt) {
}
@EventHandler
public void preInit(FMLPreInitializationEvent event) {
}
@EventHandler
public void postInit(FMLPostInitializationEvent evt) {
}
}
The MANIFEST file inside mod's jarfile :
MANIFEST.MF
Manifest-Version: 1.0
FMLCorePlugin: pkg.SkinFMLLoadingPlugin
-Dfml.coreMods.load=pkg.SkinFMLLoadingPlugin
And now the stacktrace, and I repeat that it happen ONLY WHEN TESTING OUTSIDE ECLIPSE :
Client> 2014-06-05 00:13:39 [GRAVE] [ForgeModLoader] Unable to launch
Client> java.lang.reflect.InvocationTargetException
Client> at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
Client> at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
Client> at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
Client> at java.lang.reflect.Method.invoke(Unknown Source)
Client> at net.minecraft.launchwrapper.Launch.launch(Launch.java:131)
Client> at net.minecraft.launchwrapper.Launch.main(Launch.java:27)
Client> Caused by: java.lang.NoClassDefFoundError: net/minecraft/client/entity/EntityClientPlayerMP
Client> at net.minecraft.client.main.Main.main(SourceFile:37)
Client> ... 6 more
Client> Caused by: java.lang.ClassNotFoundException: net.minecraft.client.entity.EntityClientPlayerMP
Client> at net.minecraft.launchwrapper.LaunchClassLoader.findClass(LaunchClassLoader.java:186)
Client> at java.lang.ClassLoader.loadClass(Unknown Source)
Client> at java.lang.ClassLoader.loadClass(Unknown Source)
Client> ... 7 more
Client> Caused by: java.lang.NoClassDefFoundError: net/minecraft/client/entity/EntityPlayerSP
Client> at java.lang.ClassLoader.defineClass1(Native Method)
Client> at java.lang.ClassLoader.defineClass(Unknown Source)
Client> at java.security.SecureClassLoader.defineClass(Unknown Source)
Client> at net.minecraft.launchwrapper.LaunchClassLoader.findClass(LaunchClassLoader.java:178)
Client> ... 9 more
Client> Caused by: java.lang.ClassNotFoundException: net.minecraft.client.entity.EntityPlayerSP
Client> at net.minecraft.launchwrapper.LaunchClassLoader.findClass(LaunchClassLoader.java:186)
Client> at java.lang.ClassLoader.loadClass(Unknown Source)
Client> at java.lang.ClassLoader.loadClass(Unknown Source)
Client> ... 13 more
Client> Caused by: java.lang.NoClassDefFoundError: net/minecraft/client/entity/AbstractClientPlayer
Client> at java.lang.ClassLoader.defineClass1(Native Method)
Client> at java.lang.ClassLoader.defineClass(Unknown Source)
Client> at java.security.SecureClassLoader.defineClass(Unknown Source)
Client> at net.minecraft.launchwrapper.LaunchClassLoader.findClass(LaunchClassLoader.java:178)
Client> ... 15 more
Client> Caused by: java.lang.ClassNotFoundException: net.minecraft.client.entity.AbstractClientPlayer
Client> at net.minecraft.launchwrapper.LaunchClassLoader.findClass(LaunchClassLoader.java:186)
Client> at java.lang.ClassLoader.loadClass(Unknown Source)
Client> at java.lang.ClassLoader.loadClass(Unknown Source)
Client> ... 19 more
Client> Caused by: java.lang.RuntimeException: java.lang.ClassNotFoundException: bic
Client> at org.objectweb.asm.ClassWriter.getCommonSuperClass(Unknown Source)
Client> at org.objectweb.asm.ClassWriter.a(Unknown Source)
Client> at org.objectweb.asm.Frame.a(Unknown Source)
Client> at org.objectweb.asm.Frame.a(Unknown Source)
Client> at org.objectweb.asm.MethodWriter.visitMaxs(Unknown Source)
Client> at org.objectweb.asm.tree.MethodNode.accept(Unknown Source)
Client> at org.objectweb.asm.tree.MethodNode.accept(Unknown Source)
Client> at org.objectweb.asm.tree.ClassNode.accept(Unknown Source)
Client> at vertebro.SkinClassTransformer.patchClassASM(SkinClassTransformer.java:86)
Client> at vertebro.SkinClassTransformer.transform(SkinClassTransformer.java:23)
Client> at net.minecraft.launchwrapper.LaunchClassLoader.runTransformers(LaunchClassLoader.java:274)
Client> at net.minecraft.launchwrapper.LaunchClassLoader.findClass(LaunchClassLoader.java:172)
Client> ... 21 more
Thanks !