ASM INVOKE et obfuscation



  • Bonjour,

    Je vais poster mon premier "casse tête" 😄

    Contexte:

    • Je développe actuellement un coremod dans lequel j’utilise pas mal d'ASM
    • Minecraft version: 1.7.10
    • Forge build: 1.7.10-10.13.4.1614-1.7.10
    • Java: 1.8.0_66
    • Le mod est server side only

    J’utilise l'ASM pour modifier la partie handshake et login des joueurs pour gérer plusieurs protocole dont celui de l'ip forwarding de bungeecord.
    Je procède toujours de la même façon, dans une méthode donné, je rajouter un appel via INVOKESTATIC par exemple pour appeler une fonction static de mes class qui vont modifier le fonctionnement de la fonction.

    Jusque la, pas de soucis, en environnement de dev tout marche nikel, le probleme surviens quand je veux distribuer le mod, et le tester en "production".
    Mon injection fonctionne nikel, ma fonction ajouté est bien appelé, mais par exemple si à l'intérieur je veux utiliser un ChatComponentText pour "kick" le joueur avec un message d’erreur, je revoit une java.lang.NoClassDefFoundError sur net/minecraft/util/IChatComponent.

    Du coup ma fonctionne marche nikel si j’enlève la partie message derreur, mais ça me gêne pas mal, j’aimerai comprendre comment corriger ça.

    Je peux faire un code allégé pour montrer mon problème si besoin


  • Administrateurs

    Salut,
    Possible d'avoir le crash complet ?



  • @'robin4002':

    Salut,
    Possible d'avoir le crash complet ?

    Bien sur, mais je suis pas sur que ça vas t’aider 😄

    A problem occurred running the Server launcher.[16:03:59] [main/INFO] [STDERR]: [java.lang.Throwable$WrappedPrintStream:println:-1]: java.lang.reflect.InvocationTargetException
    [16:03:59] [main/INFO] [STDERR]: [java.lang.Throwable$WrappedPrintStream:println:-1]:   at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    [16:03:59] [main/INFO] [STDERR]: [java.lang.Throwable$WrappedPrintStream:println:-1]:   at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    [16:03:59] [main/INFO] [STDERR]: [java.lang.Throwable$WrappedPrintStream:println:-1]:   at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    [16:03:59] [main/INFO] [STDERR]: [java.lang.Throwable$WrappedPrintStream:println:-1]:   at java.lang.reflect.Method.invoke(Unknown Source)
    [16:03:59] [main/INFO] [STDERR]: [java.lang.Throwable$WrappedPrintStream:println:-1]:   at cpw.mods.fml.relauncher.ServerLaunchWrapper.run(ServerLaunchWrapper.java:43)
    [16:03:59] [main/INFO] [STDERR]: [java.lang.Throwable$WrappedPrintStream:println:-1]:   at cpw.mods.fml.relauncher.ServerLaunchWrapper.main(ServerLaunchWrapper.java:12)
    [16:03:59] [main/INFO] [STDERR]: [java.lang.Throwable$WrappedPrintStream:println:-1]: Caused by: java.lang.NoClassDefFoundError: net/minecraft/util/IChatComponent
    [16:03:59] [main/INFO] [STDERR]: [java.lang.Throwable$WrappedPrintStream:println:-1]:   at fr.littlebigcraft.asm.MasterClassTransformer.<clinit>(MasterClassTransformer.java:53)
    [16:03:59] [main/INFO] [STDERR]: [java.lang.Throwable$WrappedPrintStream:println:-1]:   at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    [16:03:59] [main/INFO] [STDERR]: [java.lang.Throwable$WrappedPrintStream:println:-1]:   at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
    [16:03:59] [main/INFO] [STDERR]: [java.lang.Throwable$WrappedPrintStream:println:-1]:   at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
    [16:03:59] [main/INFO] [STDERR]: [java.lang.Throwable$WrappedPrintStream:println:-1]:   at java.lang.reflect.Constructor.newInstance(Unknown Source)
    [16:03:59] [main/INFO] [STDERR]: [java.lang.Throwable$WrappedPrintStream:println:-1]:   at java.lang.Class.newInstance(Unknown Source)
    [16:03:59] [main/INFO] [STDERR]: [java.lang.Throwable$WrappedPrintStream:println:-1]:   at net.minecraft.launchwrapper.LaunchClassLoader.registerTransformer(LaunchClassLoader.java:88)
    [16:03:59] [main/INFO] [STDERR]: [java.lang.Throwable$WrappedPrintStream:println:-1]:   at cpw.mods.fml.relauncher.CoreModManager$FMLPluginWrapper.injectIntoClassLoader(CoreModManager.java:108)
    [16:03:59] [main/INFO] [STDERR]: [java.lang.Throwable$WrappedPrintStream:println:-1]:   at net.minecraft.launchwrapper.Launch.launch(Launch.java:115)
    [16:03:59] [main/INFO] [STDERR]: [java.lang.Throwable$WrappedPrintStream:println:-1]:   at net.minecraft.launchwrapper.Launch.main(Launch.java:28)
    [16:03:59] [main/INFO] [STDERR]: [java.lang.Throwable$WrappedPrintStream:println:-1]:   … 6 more
    [16:03:59] [main/INFO] [STDERR]: [java.lang.Throwable$WrappedPrintStream:println:-1]: Caused by: java.lang.ClassNotFoundException: net.minecraft.util.IChatComponent
    [16:03:59] [main/INFO] [STDERR]: [java.lang.Throwable$WrappedPrintStream:println:-1]:   at net.minecraft.launchwrapper.LaunchClassLoader.findClass(LaunchClassLoader.java:191)
    [16:03:59] [main/INFO] [STDERR]: [java.lang.Throwable$WrappedPrintStream:println:-1]:   at java.lang.ClassLoader.loadClass(Unknown Source)
    [16:03:59] [main/INFO] [STDERR]: [java.lang.Throwable$WrappedPrintStream:println:-1]:   at java.lang.ClassLoader.loadClass(Unknown Source)
    [16:03:59] [main/INFO] [STDERR]: [java.lang.Throwable$WrappedPrintStream:println:-1]:   … 16 more
    [16:03:59] [main/INFO] [STDERR]: [java.lang.Throwable$WrappedPrintStream:println:-1]: Caused by: java.lang.NullPointerException
    [16:03:59] [main/INFO] [STDERR]: [java.lang.Throwable$WrappedPrintStream:println:-1]:   at net.minecraft.launchwrapper.LaunchClassLoader.findClass(LaunchClassLoader.java:182)
    [16:03:59] [main/INFO] [STDERR]: [java.lang.Throwable$WrappedPrintStream:println:-1]:   … 18 more
    
    ```</clinit>

  • Administrateurs

    En effet.
    Je m'attendais à voir dans le deuxième ClassNotFoundException un détail, dès fois il indique que le byte code de la classe en question est invalide par exemple.

    Mais là ce n'est pas le cas, donc c'est juste qu'il ne trouve pas la classe comme si elle n'était pas là …

    Le mod est sur un serveur forge ou cauldron ?

    Maintenant je comprends pourquoi tu parlais de problème tordu x)

    ÉDIT : tu pourrai aussi envoyer le code de la classe MasterClassTransformer ?



  • @'robin4002':

    Maintenant je comprends pourquoi tu parlais de problème tordu x)

    J'avais prévenu 😉

    Le serveur est pure forge, pour mes test j'utilise un serveur forge de la version 1.7.10-10.13.4.1614-1.7.10 sans aucun autre mod, avec comme seul librairy ajouté le connector mySQL (mais pas utilisé pour cette partie du code).

    Concernant les bytecode je pense avoir plutôt bien géré cette partie la, car en environnement deobfuscate ça marche nikel, et en obfuscate ça marche si j'envoie pas ce fichu ChatConponant au joueur; mais si j'affiche un bête message dans la console il apparait bien :s

    PS: concernant

    [16:03:59] [main/INFO] [STDERR]: [java.lang.Throwable$WrappedPrintStream:println:-1]:   at fr.littlebigcraft.asm.MasterClassTransformer.<clinit>(MasterClassTransformer.java:53)
    

    Je fait un simple ajout dans une liste d'un objet qui soccupe de modifier le bytcode

    cTransformerList.add(new NetHandlerHandshakeTCPTransformer());
    

    et la class NetHandlerHandshakeTCPTransformer n'a pas de constructeur définit</clinit>



  • Alors je vais envoyer seulement les partie utile, car le code est plutôt long 😄

    private static final List <iclassnodetransformer>cTransformerList = new ArrayList<iclassnodetransformer>();
    
    […]
    
    static {
       cTransformerList.add(new C00HandshakeTransformer());
       cTransformerList.add(new NetworkManagerTransformer());
       cTransformerList.add(new NetHandlerHandshakeTCPTransformer()); // la ligne 53
       cTransformerList.add(new NetHandlerLoginServerTransformer());
    }
    
    […]
    
    @Override
    public byte[] transform(String name, String transformedName, byte[] bytes) {
    
       if (bytes == null)
           return null;
    
       for (IClassNodeTransformer ct : cTransformerList) {
        if (transformedName.equals(ct.getClassName())) {
        boolean isObfuscate = !name.equals(transformedName);
               ClassNode classNode = new ClassNode();
               ClassReader classReader = new ClassReader(bytes);
               classReader.accept(classNode, 0);
    
               ct.transform(classNode, isObfuscate);
    
               ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
               classNode.accept(classWriter);
               bytes = classWriter.toByteArray();
        break;
        }
       }
    
       return bytes;
    }
    
    […]
    

    Edit: la partie transform de la class NetHandlerHandshakeTCPTransformer

    @Override
    public void        transform(ClassNode classNode, boolean isObfuscate) {
        final String    targetMethod        = isObfuscate ? "a" : "processHandshake";
        final String    targetMethodDesc    = isObfuscate ? "(Ljp;)V" : "(Lnet/minecraft/network/handshake/client/C00Handshake;)V";
    
        for (MethodNode m : classNode.methods) {
            if ((m.name.equals(targetMethod) && (m.desc.equals(targetMethodDesc)))) {
                   CatLog.info("Injection du NetHandlerHandshakeTCP: processHandshake");
    
                   InsnList    newBlock    = new InsnList();
    
                   newBlock.add(new VarInsnNode(ALOAD, 1));
                   newBlock.add(new VarInsnNode(ALOAD, 0));
    
                   if (isObfuscate)
                       newBlock.add(new FieldInsnNode(GETFIELD, "nl", "b", "Lej;"));
                   else
                       newBlock.add(new FieldInsnNode(GETFIELD, "net/minecraft/server/network/NetHandlerHandshakeTCP", "field_147386_b", "Lnet/minecraft/network/NetworkManager;"));
    
                final String    methodDesc        = isObfuscate ? "(Ljp;Lej;)V" : "(Lnet/minecraft/network/handshake/client/C00Handshake;Lnet/minecraft/network/NetworkManager;)V";
    
                   newBlock.add(new MethodInsnNode(INVOKESTATIC, "fr/littlebigcraft/transformers/NetHandlerHandshakeTCPTransformer", "onProcessHandshakeStart", methodDesc, false));
    
                   m.instructions.insert(newBlock);
    
                   break;
            }
        }
    }
    ```</iclassnodetransformer></iclassnodetransformer>

  • Administrateurs

    Et du-coup j'imagine que net.minecraft.util.IChatComponent est utilisé quelque part dans la classe NetHandlerHandshakeTCPTransformer ?

    Ce qui cause surement problème c'est donc la façon dont le jeu en prod fonctionne.

    En dev c'est très simple comme tous passe les noms proposés par les mappings (=nom mcp).
    Un prod c'est plus complexe, de base le code est comme fourni par mojang (100% obf, donc avec des classes nommées a, aa, etc … et des fonctions/fields du même nom = nom notch). Lors du lancement, le byte code passe par un transformer qui fait donne un nom correcte aux classes et nomme les field et fonction en field_xxxx et func_xxx (=nom srg).
    Le transformer des différents coremod est surement appliqué au même moment ou avant.
    Du-coup au moment où ton mod cherche "net.minecraft.util.IChatComponent", il devrait plutôt chercher "fj"

    Donc faudrait soit get la classe fj par réflexion, soit faire en sorte qu'elle soit chargé plus tard.



  • En gros j'ai une autre method static dans cette class comme ca:

    public static void    onProcessHandshakeStart(C00Handshake packetIn, NetworkManager networkManager) {
    

    et c'est la dedans que j'utilise le ChatConponent
    Elle est appelé via ASM comme ceci:

    final String    methodDesc        = isObfuscate ? "(Ljp;Lej;)V" : "(Lnet/minecraft/network/handshake/client/C00Handshake;Lnet/minecraft/network/NetworkManager;)V";
    
    newBlock.add(new MethodInsnNode(INVOKESTATIC, "fr/littlebigcraft/transformers/NetHandlerHandshakeTCPTransformer", "onProcessHandshakeStart", methodDesc, false));
    
    

    C'est pas grave si j'ai pas de solution, je vais rajouter le code de ma fonction entièrement en bytecode, comme ca aucun prob dobfuscation car je pourrai le gérer manuellement.



  • Problème résolu en réglant l'autre problème mystérieux,

    Merci pour ta patience.