I will briefly explain how to write a core mod in the Minecraft Forge 1.15.2 environment. What is ASM? What is a byte code? I will not explain it here. Excuse me. I will not explain Javascript either. Excuse me.
However, if you are a modder who uses core mods, you can write with a feeling.
Forge 1.13 or later will probably work in the same way, but I haven't verified it.
I have very much referred to this page and the sample code. I am deeply grateful to my predecessors. 99.99 - Coremod
Still easy. "coremod" is one of the mechanisms provided by Forge, and you can use it to rewrite the Java code of Minecraft itself (the expression is a little misleading). The library used for that is ASM. Also, the code itself is not the human code that you normally write, but the compiled bytecode. In any case, if you write ASM, you will be able to do anything. However, competition with other mods may increase.
This pdf is highly recommended (but long) for studying ASM. ASM 4.0 A Java bytecode engineering library
Minecraft: 1.15.2 Minecraft Forge: 1.15.2-31.2.0
Previously, coremod was written in Java code, but from 1.13, it will be written in ** Javascript **. It's okay to think that ASM itself is written in the same way as before (probably).
At least two files are required.
coremods.json Place it in ** main / java / resources / META-INF / directly under the folder **. The file name should probably be "coremods.json".
The contents are as below.
coremods.js
{
"TestMod Transformer": "testmod-transformer.js"
}
The key in the array ("TestMod Transformer") is probably anything. I think it doesn't matter what the case. For the value, write the location of the js file that you will write.
If you just write the file name as above, it means that you are specifying the file in ** main / java / resources / directly under **. I haven't tried whether the absolute path or the relative path works, but I feel that the relative path seems to work.
coremods.js
{
"TestMod Transformer": "testmod-transformer.js",
"TestMod Transformer2": "testmod-transformer2.js"
}
You can also specify multiple javascript files at once.
For the sake of clarity, let's put it directly under main / java / resources /. If you change the location, please change the value of the contents of coremods.json above. The js that actually writes ASM looks like the following.
testmod-transformer.js
function initializeCoreMod() {
return {
'coremodmethod': {
'target': {
'type': 'METHOD',
'class': 'your.target.class',
'methodName': 'targetMethodName',
'methodDesc': 'targetMethodDescriptor'
},
'transformer': function(method) {
//Write ASM processing here.
return method;
}
}
}
}
In the js file, you need to write a function that returns json with the name "initializeCoreMod ()". It's OK if you copy and paste on the top. If you write various things in the above "Write ASM processing here", it is completed.
Thank you for your hard work. Then, huh, do you usually come here? It seems to be said, so I will write a little more. If you understand the above, feel free to do the rest.
You can specify three types of targets: "FIELD", "METHOD", and "CLASS". It interferes with fields, methods, and classes, respectively. In other words, you can write the following three ways.
testmod-transformer.js
function initializeCoreMod() {
return {
'coremodmethod': {
'target': {
'type': 'FIELD',
'class': 'your.target.class',
'fieldName': 'targetFieldName'
},
'transformer': function(field) {
//Write ASM processing here.
return field;
}
}
}
}
testmod-transformer.js
function initializeCoreMod() {
return {
'coremodmethod': {
'target': {
'type': 'METHOD',
'class': 'your.target.class',
'methodName': 'targetMethodName',
'methodDesc': 'targetMethodDescriptor'
},
'transformer': function(method) {
//Write ASM processing here.
return method;
}
}
}
}
testmod-transformer.js
function initializeCoreMod() {
return {
'coremodmethod': {
'target': {
'type': 'CLASS',
'class': 'your.target.class'
},
'transformer': function(class) {
//Write ASM processing here.
return class;
}
}
}
}
The required values in "target" are as above. However, the field can be interfered with from Java with the reflection helper, and I feel that it is often enough to interfere with the method without interfering with the class itself, so for the personal reason, I will modify FIELD and CLASS below. Will not touch. Someone please.
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;
}
}
}
}
I think the outlook has improved considerably. I will explain in order.
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");
I don't know what kind of magic it is, but if you put the usual import destination class name in the argument of "Java.type ()", you can use it as if it were Java (explanation miscellaneous). If Opcodes comes, I feel like I have a hundred people. But I'm having trouble with code completion not working. If anyone knows how to complement, please let me know. I'm sure ASMAPI and Opcodes will use any modifications, but I think it's up to you to call other classes. The current coremod uses the so-called tree API, so please check there separately to find out what classes are available.
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;
}
}
}
I named it "coremodmethod", but it seems that this can be any string. Other "target" and "transformer" must be this.
A method called mapMethod () is defined in ASMAPI, and it returns the mapped method name from the obfuscated method name. For example, the "saveScreenshotRaw" method of the "net.minecraft.util.ScreenShotHelper" class is "func_228051_b_" or something like that. If you don't get the method name using this mapMethod (), it will work in the development environment but not in the real environment (or vice versa).
When actually writing, put the obfuscated method name in the argument such as "ASMAPI.mapMethod (" func_228051_b_ ")".
For the method name after obfuscation, select the mapping that suits your development environment from the usual MCP Bot and search for it.
By the way, there are some people who can't find it even if they look for it by MCP mapping. You don't need to fetch the method name with mapMethod () because they already work in the development environment and the real environment with the name that is easy to understand.
"methodDesc" is the usual descriptor.
void hogehoge(int i, float f)
If it is a method like that, the descriptor will be "(IF) V", which is the usual thing.
Both IDEA and Eclipse have plug-ins that display Java code in bytecode, so I think that using it will improve copy and paste.
'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;
}
I personally think that the parts that are actually modified are easier to write than before.
The method that crosses the argument has a field called instructions, which contains bytecode line by line in an array. The method bytecode itself is. After that, you can delete, change, or insert a return at any position in the bytecode.
In the above, we have made a modification that "returns on the spot when a certain method is called". Primitively, it turns instructions one by one with for to find the line (that is, instructions) where the target method is called.
When you find the instruction you are looking for, insert a new instruction.
InsnList () is a class for creating a series of instruction pairs, and each instruction is added () to this class. Here, as "new InsnNode (Opcodes.RETURN)", only the instruction that means "return" is added to the InsnList.
Finally, instructions has a method called insert (argument 1, argument 2), which adds the instructions inside the insnList specified in argument 2 immediately after ** immediately after the instruction specified in argument 1. I will.
Here, it is the process of adding "return" immediately after a specific method.
It's important to note that if you've touched coremod, you'll know that if you return as much as you like, the frames will be inconsistent and will probably crash in the current version as well. I think that values that are loaded in the frame but are no longer used must be popped the correct number of times.
--I don't know the reason, but you can't use let or const when declaring a variable. An error will occur. Use var. --Print () outputs to the console when you debug and start Minecraft in the development environment, but it seems that it does not appear anywhere in the real environment. ――It seems that you can return multiple transformers with one javascript file, but I'm not sure how to do it. --Forge's ASMAPI class (which is usually Java) seems to have a method to find the place where a method is called first, so I think it's a good idea to take a look at the contents once. ..
I thought I'd write an example, but it helped me. I will write it again if I have some spare capacity.
I feel that there are various mistakes and misunderstandings, so please let me know if you notice.
Thank you for your relationship.
Recommended Posts