[JAVA] Comment écrire un mod de base dans Minecraft Forge 1.15.2

Je vais expliquer brièvement comment écrire un mod de base dans l'environnement Minecraft Forge 1.15.2. Qu'est-ce que l'ASM? Qu'est-ce qu'un code d'octet? Je ne vais pas l'expliquer ici. Excusez-moi. Je n'expliquerai pas non plus Javascript. Excusez-moi.

Cependant, si vous êtes un moddeur qui utilise des mods de base, vous pouvez écrire avec un sentiment.

Forge 1.13 ou version ultérieure fonctionnera probablement de la même manière, mais je ne l'ai pas vérifié.

J'ai beaucoup fait référence à cette page et à l'exemple de code. Je suis profondément reconnaissant à mes prédécesseurs. 99.99 - Coremod

Qu'est-ce que coremod? Qu'est-ce que l'ASM?

Toujours facile. "coremod" est l'un des mécanismes fournis par Forge, qui vous permet de réécrire le code Java de Minecraft lui-même (il y a un malentendu). La bibliothèque utilisée pour cela est ASM. De plus, le code lui-même n'est pas le code humain que vous écrivez habituellement, mais le code d'octet qui a été compilé. Dans tous les cas, si vous écrivez ASM, vous pourrez tout faire. Cependant, la concurrence avec d'autres mods peut augmenter.

Ce pdf est fortement recommandé (mais long) pour étudier l'ASM. ASM 4.0 A Java bytecode engineering library

Environnement de vérification

Minecraft: 1.15.2 Minecraft Forge: 1.15.2-31.2.0

Changements par rapport à la version 1.13

Auparavant, coremod était écrit en code Java, mais à partir de la 1.13, il sera écrit en ** Javascript **. Il est normal de penser que l'ASM lui-même est écrit de la même manière qu'avant (probablement).

organisation des fichiers

Au moins deux fichiers sont requis.

coremods.json Placez-le directement sous ** main / java / resources / META-INF / folder **. Le nom du fichier devrait probablement être "coremods.json".

Le contenu est comme ci-dessous.

coremods.js


{
  "TestMod Transformer": "testmod-transformer.js"
}

La clé du tableau ("Test Mod Transformer") est probablement n'importe quoi. Je pense que peu importe le cas. Pour la valeur, écrivez l'emplacement du fichier js que vous allez écrire.

Si vous écrivez simplement le nom du fichier comme ci-dessus, vous spécifiez le fichier dans ** main / java / resources / directement sous **. Je n'ai pas essayé si le chemin absolu ou le chemin relatif fonctionne, mais je pense que le chemin relatif semble fonctionner.

coremods.js


{
  "TestMod Transformer": "testmod-transformer.js",
  "TestMod Transformer2": "testmod-transformer2.js"
}

Vous pouvez également spécifier plusieurs fichiers javascript à la fois.

Fichier Javascript pour écrire ASM

Par souci de clarté, mettons-le directement sous main / java / resources /. Si vous modifiez l'emplacement, veuillez modifier la valeur du contenu de coremods.json ci-dessus. Le js qui écrit réellement ASM ressemble à ce qui suit.

testmod-transformer.js


function initializeCoreMod() {
    return {
        'coremodmethod': {
            'target': {
                'type': 'METHOD',
                'class': 'your.target.class',
                'methodName': 'targetMethodName',
                'methodDesc': 'targetMethodDescriptor'
            },
            'transformer': function(method) {
                //Écrivez le traitement ASM ici.
                return method;
            }
        }
    }
}

Dans le fichier js, vous devez écrire une fonction nommée "initializeCoreMod ()" qui renvoie json. Ce n'est pas grave si vous copiez et collez sur le dessus. Si vous écrivez diverses choses dans la section «Écrire le traitement ASM ici» ci-dessus, cela est terminé.

Je vous remercie pour votre travail acharné.               Alors, tu viens habituellement ici? Cela semble être dit, alors j'écrirai un peu plus. Si vous comprenez ce qui précède, n'hésitez pas à faire le reste.

Type de transformateur

Vous pouvez spécifier trois types de cibles: "FIELD", "METHOD" et "CLASS". Cela interfère avec les champs, les méthodes et les classes, respectivement. En d'autres termes, vous pouvez écrire les trois manières suivantes.

testmod-transformer.js


function initializeCoreMod() {
    return {
        'coremodmethod': {
            'target': {
                'type': 'FIELD',
                'class': 'your.target.class',
                'fieldName': 'targetFieldName'
            },
            'transformer': function(field) {
                //Écrivez le traitement ASM ici.
                return field;
            }
        }
    }
}

testmod-transformer.js


function initializeCoreMod() {
    return {
        'coremodmethod': {
            'target': {
                'type': 'METHOD',
                'class': 'your.target.class',
                'methodName': 'targetMethodName',
                'methodDesc': 'targetMethodDescriptor'
            },
            'transformer': function(method) {
                //Écrivez le traitement ASM ici.
                return method;
            }
        }
    }
}

testmod-transformer.js


function initializeCoreMod() {
    return {
        'coremodmethod': {
            'target': {
                'type': 'CLASS',
                'class': 'your.target.class'
            },
            'transformer': function(class) {
                //Écrivez le traitement ASM ici.
                return class;
            }
        }
    }
}

Les valeurs requises dans "cible" sont comme ci-dessus. Cependant, pour la raison personnelle que les champs peuvent être interférés à partir de Java avec l'assistant de réflexion, et il semble que l'interférence avec la méthode soit assez souvent sans interférer avec la classe elle-même, ce qui suit est une modification de FIELD et CLASS. Ne touchera pas. Quelqu'un s'il vous plaît.

Comment écrire concrètement

function initializeCoreMod() {
    var ASMAPI = Java.type("net.minecraftforge.coremod.api.ASMAPI");
    var Opcodes = Java.type('org.objectweb.asm.Opcodes');
    var InsnList = Java.type("org.objectweb.asm.tree.InsnList");
    var InsnNode = Java.type("org.objectweb.asm.tree.InsnNode");

    var mappedMethodName = ASMAPI.mapMethod("target_srg_func_name");

    return {
        'coremodmethod': {
            'target': {
                'type': 'METHOD',
                'class': 'your.target.class',
                'methodName': mappedMethodName,
                'methodDesc':'targetMethodDescriptor'
            },
            'transformer': function(method) {
                print("Enter transformer.");

                var arrayLength = method.instructions.size();
                var target_instruction = null;

                //search the target instruction from the entire instructions.
                for(var i = 0; i < arrayLength; ++i) {
                    var instruction = method.instructions.get(i);
                    if(instruction.name === "targetMethodName") {
                        target_instruction = instruction;
                        break;
                    }
                }

                if(target_instruction == null) {
                    print("Failed to detect target.");
                    return method;
                }

                var toInject = new InsnList();
//              toInject.add(new InsnNode(Opcodes.POP));
                toInject.add(new InsnNode(Opcodes.RETURN));

                // Inject new instructions just after the target.
                method.instructions.insert(target_instruction, toInject);

                return method;
            }
        }
    }
}

Je pense que les perspectives se sont considérablement améliorées. Je vais expliquer dans l'ordre.

Appel de classe Java

    var ASMAPI = Java.type("net.minecraftforge.coremod.api.ASMAPI");
    var Opcodes = Java.type('org.objectweb.asm.Opcodes');
    var InsnList = Java.type("org.objectweb.asm.tree.InsnList");
    var InsnNode = Java.type("org.objectweb.asm.tree.InsnNode");

Je ne sais pas de quel genre de magie il s'agit, mais si vous mettez le nom habituel de la classe de destination d'importation dans l'argument de "Java.type ()", vous pouvez l'utiliser comme s'il s'agissait de Java (explication divers). Si Opcodes arrive, je sens qu'il y a déjà 100 personnes. Mais j'ai des problèmes avec l'achèvement du code qui ne fonctionne pas. Si quelqu'un sait comment compléter, faites-le moi savoir. Je suis sûr qu'ASMAPI et Opcodes utiliseront n'importe quelle modification, mais je pense que c'est votre préférence d'appeler d'autres classes. Le coremod actuel utilise ce qu'on appelle l'API d'arborescence, veuillez donc vérifier séparément pour voir quelles classes sont disponibles.

Spécification des méthodes interférentes

    var mappedMethodName = ASMAPI.mapMethod("target_srg_func_name");

    return {
        'coremodmethod': {
            'target': {
                'type': 'METHOD',
                'class': 'your.target.class',
                'methodName': mappedMethodName,
                'methodDesc':'targetMethodDescriptor'
            },
            'transformer': function(method) {
                return method;
            }
        }
    }

Je l'ai nommé "coremodmethod", mais il semble que cela puisse être n'importe quelle chaîne de caractères. Les autres «cibles» et «transformateurs» doivent être ceci.

Une méthode appelée mapMethod () est définie dans ASMAPI et renvoie le nom de la méthode mappée à partir du nom de la méthode masquée. Par exemple, la méthode "saveScreenshotRaw" de la classe "net.minecraft.util.ScreenShotHelper" est "func_228051_b_" ou quelque chose comme ça. Si vous n'obtenez pas le nom de la méthode en utilisant cette mapMethod (), cela fonctionnera dans l'environnement de développement mais pas dans l'environnement réel (ou vice versa).

Lors de l'écriture, placez le nom de la méthode masquée dans l'argument tel que "ASMAPI.mapMethod (" func_228051_b_ ")".

Pour le nom de la méthode après obfuscation, sélectionnez le mappage qui convient à votre environnement de développement à partir du [Bot MCP] habituel (http://export.mcpbot.bspk.rs/).

À propos, il y a des gens qui ne peuvent pas le trouver même s'ils le recherchent par cartographie MCP. Vous n'avez pas besoin de récupérer le nom de la méthode avec mapMethod () car elles s'exécutent déjà sous le nom qui est facile à comprendre à la fois dans l'environnement de développement et dans l'environnement réel.

"methodDesc" est le descripteur habituel.

void hogehoge(int i, float f)

S'il s'agit d'une méthode comme celle-là, le descripteur sera "(IF) V".

IDEA et Eclipse ont tous deux un plug-in qui affiche le code Java en byte code, donc je pense que son utilisation améliorera la copie.

Réorganisation réelle

'transformer': function(method) {
    print("Enter transformer.");
    
    var arrayLength = method.instructions.size();
    var target_instruction = null;
    
    //search the target instruction from the entire instructions.
    for(var i = 0; i < arrayLength; ++i) {
        var instruction = method.instructions.get(i);
        if(instruction.name === "targetMethodName") {
            target_instruction = instruction;
            break;
        }
    }

    if(target_instruction == null) {
        print("Failed to detect target.");
        return method;
    }

    var toInject = new InsnList();
//  toInject.add(new InsnNode(Opcodes.POP));
    toInject.add(new InsnNode(Opcodes.RETURN));
    
    // Inject new instructions just after the target.
    method.instructions.insert(target_instruction, toInject);
    
    return method;
}

Je pense personnellement que les parties qui sont réellement modifiées sont plus faciles à écrire qu'auparavant.

La méthode qui croise l'argument a un champ appelé instructions, dans lequel le code d'octet est entré ligne par ligne dans un tableau. Le bytecode de la méthode elle-même est. Après cela, vous pouvez supprimer, modifier ou insérer un retour à n'importe quelle position dans le code d'octet.

Dans ce qui précède, nous avons fait une modification qui "revient sur place quand une certaine méthode est appelée". Commencez par tourner les instructions une par une avec for pour trouver la ligne (c'est-à-dire les instructions) où la méthode cible est appelée.

Lorsque vous trouvez l'instruction que vous recherchez, insérez une nouvelle instruction.

InsnList () est une classe pour créer une série de paires d'instructions, et chaque instruction est ajoutée () à cette classe. Ici, en tant que "new InsnNode (Opcodes.RETURN)", seule l'instruction qui signifie "return" est ajoutée à l'InsnList.

Enfin, les instructions ont une méthode appelée insert (argument 1, argument 2), qui ajoute les instructions à l'intérieur de la insnList spécifiée dans l'argument 2 immédiatement après ** immédiatement après l'instruction spécifiée dans l'argument 1. Je vais.

Ici, c'est le processus d'ajout de «retour» immédiatement après une méthode spécifique.

Il est important de noter que si vous avez touché coremod, vous saurez que si vous revenez autant que vous le souhaitez, les cadres deviendront incohérents et planteront probablement également dans la version actuelle. Je pense que les valeurs qui sont chargées dans le cadre mais qui ne sont plus utilisées doivent être sautées le bon nombre de fois.

Autre

--Je ne connais pas la raison, mais vous ne pouvez pas utiliser let ou const lors de la déclaration d'une variable. Une erreur se produira. Utilisez var. --print () sort sur la console lorsque vous déboguez et démarrez Micra dans l'environnement de développement, mais il semble qu'il n'apparaisse nulle part dans l'environnement réel. ――Il semble que vous puissiez renvoyer plusieurs transformateurs avec un fichier javascript, mais je ne sais pas comment le faire.

À la fin

Je pensais écrire un exemple, mais cela m'a aidé. Je l'écrirai à nouveau si j'ai une certaine capacité de réserve.

Je pense qu'il y a diverses erreurs et malentendus, alors faites-le moi savoir si vous remarquez.

Merci pour votre relation.

Recommended Posts

Comment écrire un mod de base dans Minecraft Forge 1.15.2
Comment signer Minecraft MOD
Comment écrire une recherche de comparaison de dates dans Rails
[Basique] Comment écrire un auto-apprentissage Dockerfile ②
Comment insérer une vidéo dans Rails
[Introduction à Java] Comment écrire un programme Java
Comment publier une bibliothèque dans jCenter
[SpringBoot] Comment écrire un test de contrôleur
Rails: comment bien écrire une tâche de râteau
Ajoutez des armes originales aux mods avec Minecraft Forge 1.15.2
[Java FX] Comment écrire des autorisations Eclipse dans build.gradle
[Rails] Comment écrire lors de la création d'une sous-requête
Comment afficher une page Web en Java
Comment modifier un calque avec Core ML Tools
Comment exécuter une tâche djUnit dans Ant
Comment ajouter un chemin de classe dans Spring Boot
Comment créer un thème dans Liferay 7 / DXP
Comment implémenter une fonctionnalité similaire dans Rails
Comment créer facilement un pull-down avec des rails
Comment écrire Java String # getBytes dans Kotlin?
Notes sur la façon de rédiger des commentaires en anglais
Comment générer automatiquement un constructeur dans Eclipse
Comment écrire des rails
Comment écrire Mockito
Comment écrire un fichier de migration
Comment effacer toutes les données d'une table particulière
Comment créer un environnement Java en seulement 3 secondes
Comment écrire un test unitaire pour Spring Boot 2
java: Comment écrire une liste de types génériques [Note]
Comment créer un projet Spring Boot dans IntelliJ
Comment créer un URI de données (base64) en Java
Comment lancer une autre commande dans un programme Ruby
Comment afficher un aperçu du navigateur avec VS Code
[Comment insérer une vidéo dans un hameau avec Rails]
Comment se moquer d'un appel de super méthode dans PowerMock
Comment convertir un fichier en tableau d'octets en Java
[Rails 6] Comment définir une image d'arrière-plan dans Rails [CSS]
[Rails] Comment charger JavaScript dans une vue spécifique
Comment écrire du bon code
Comment rédiger un commentaire java
Comment laisser un commentaire
[Refactoring] Comment écrire le routage
Comment écrire Junit 5 organisé
Comment écrire des graines de Rails
Pour écrire un programme orienté utilisateur (1)
Comment écrire le routage Rails
Comment insérer une vidéo
Comment créer une méthode
Comment écrire une migration du type Rails datetime au type date
[Forge] Comment enregistrer votre propre Entité et Entité Render dans 1.13.2