中文 English
Mod loaders like Fabric and Forge provide mappings that translate Minecraft class, method, and field names to human-readable ones. Since the mappings are created independently, it is often the case where class, method, and field names are translated differently between mod loaders and even between Minecraft versions. Even the package of a certain class can change. The purpose of this project is to provide a stable map of classes, methods, and fields across different mod loaders and versions of Minecraft.
Currently, this project supports 1.16-1.20 Fabric and Forge. For simplicity and ease of maintenance, only the last version of Minecraft for each major version is supported. For example, 1.16.5 is supported but 1.16.4 is not.
The <mod loader>/<Minecraft version>-generator
and <mod loader>/<Minecraft version>-mapping
subprojects are created for each supported version. The -mapping
subproject contains all the mapping code that will be built into a library JAR and the -generator
subproject is responsible for generating classes for the -mapping
subproject.
The @MappedMethod
annotation, applicable to constructors and methods, is used to show that a constructor or method is guaranteed to exist between versions. Constructors and methods not guaranteed to exist between versions should be marked as @Deprecated
and final or non-public.
Several JUnit tests in the build pipeline will enforce these rules.
This project relies heavily on automatic code generation to reduce repetitive manual work and potential human error. There are five stages to the build pipeline, all of which can be run using gradle commands.
Each -generator
subproject should contain a test file called ClassScannerTest
. This defines a list of classes that should be equivalent between versions. For example, in Fabric there is an Identifier
class while in Forge there is a ResourceLocation
class. Both of these are actually the same thing.
The scan()
method is used to register this list of classes. Each entry is added by scanner.put()
, scanner.putAbstract()
, or scanner.putInterface()
, which takes in a desired mapped name and the Minecraft class itself. Generally, the Fabric name is used for the mapped name. This mapped name must be consistent between all versions.
scanner.put("MappedClassName", MinecraftClass.class);
scanner.putAbstract("MappedClassName", MinecraftClass.class);
scanner.putInterface("MappedClassName", MinecraftClass.class);
As an example:
ClassScannerTest
in fabric/1.16.5-generator
scanner.put("Identifier", Identifier.class);
ClassScannerTest
in forge/1.16.5-generator
scanner.put("Identifier", ResourceLocation.class);
Classes generated using scanner.put()
will look like the following. These classes hold the actual Minecraft object inside a public final field. Enum constants will also be copied over. Note that the constructor to convert the Minecraft object to the holder class, the cast
method, and the isInstance
method is included as some helpful tools.
package org.mtr.mapping.holder;
import org.mtr.mapping.annotation.MappedMethod;
import org.mtr.mapping.tool.HolderBase;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
@ParametersAreNonnullByDefault
@SuppressWarnings({"deprecation", "unchecked", "unused"})
public final class Identifier extends HolderBase<net.minecraft.util.ResourceLocation> {
public Identifier(net.minecraft.util.ResourceLocation data) {
super(data);
}
@MappedMethod
public static Identifier cast(HolderBase<?> data) {
return new Identifier((net.minecraft.util.ResourceLocation) data.data);
}
@MappedMethod
public static boolean isInstance(HolderBase<?> data) {
return data.data instanceof net.minecraft.util.ResourceLocation;
}
// Additional class constructors, methods, and enum constants go here
}
Classes generated using scanner.putAbstract()
will look like the following. The mapped class name will always be appended by AbstractMapping
. A holder class without the AbstractMapping
suffix (see above) will also be generated.
package org.mtr.mapping.holder;
import org.mtr.mapping.annotation.MappedMethod;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
@ParametersAreNonnullByDefault
@SuppressWarnings({"deprecation", "unchecked", "unused"})
public abstract class BlockAbstractMapping extends net.minecraft.block.Block {
// Additional class constructors and methods go here
}
Classes generated using scanner.putInterface()
will look like the following. No holder classes will be generated.
package org.mtr.mapping.holder;
import org.mtr.mapping.annotation.MappedMethod;
import javax.annotation.Nonnull;
import javax.annotation.ParametersAreNonnullByDefault;
@ParametersAreNonnullByDefault
@SuppressWarnings({"deprecation", "unchecked", "unused"})
public interface StringIdentifiable extends net.minecraft.util.StringIdentifiable {
// Additional interface methods go here
}
gradle test -x common:test --rerun-tasks --continue -Pgenerate=dryRun
The first stage of the build pipeline scans each Minecraft class defined in ClassScannerTest
and saves the constructors, methods, and fields into temporary JSON files. This is done for each version. The temporary JSON files are stored in build/existingMethods
.
Step 2: From scanned classes, collect equivalent constructors, methods, and fields and generate holder classes.
gradle test -x common:test --rerun-tasks --continue -Pgenerate=normal
The second stage of the build pipeline consists of a few parts. Equivalent class, method, and field names are mapped based on manually and automatically defined mappings.
- Read manually defined method mappings in
MethodMaps
of thebuildSrc
module. They are all defined in thesetMethodMaps()
method.addMethodMap1.add()
maps method names in one or more classes. If defining models for one or more classes, separate them by the|
character.For example, theaddMethodMap1.add("ClassNames", "desiredMappedName", "additionalNamesToBeMapped");
isFood
method Fabric in theBlockItem
andItem
classes of Fabric is also calledisEdible
in Forge.addMethodMap1.add("BlockItem|Item", "isFood", "isEdible");
addMethodMap2.add()
is similar as the above except that the signature is also specified. Methods will not be mapped unless the signature matches exactly.For example, theaddMethodMap2.add("ClassNames", "desiredMappedName", "signature", "additionalNamesToBeMapped");
isChunkLoaded
method Fabric in theServerWorld
andWorld
classes of Fabric is also calledhasChunk
in Forge.addMethodMap2.add("ServerWorld|World", "isChunkLoaded", "public boolean (int,int)", "hasChunk");
blacklist.add()
is used to prevent certain methods from being mapped, for example if they cause unexpected compile errors.blacklist.add("ClassNames", "mappedName", "signature");
- Apply manually defined mappings with specified signatures.
- Attempt to automatically figure out what constructors, methods, and fields of a class are equivalent. Iterate through the remaining constructors, methods, and fields (excluding the methods mapped manually) and compare signatures. This includes modifiers, return types (methods), and object types (fields).
- If the signature and method or field name matches exactly between all versions, map it.
- If the signature matches exactly between all versions:
- If there is only one instance of the signature between all versions, assume they are the same and map it.
- If there is more than once instance of the signature between all versions, do not map anything.
- Apply manually defined mappings without signatures.
- Now that all manually defined mappings are read, repeat the automatic mapping step again.
- Collect results to
build/existingMethods/combined.json
. - Using collected results, generate class files.
- Depending on whether classes are registered with
scanner.put()
,scanner.putAbstract()
orscanner.putInterface()
, different classes will be generated. See the section above for more details about the types of generated classes. - All constructors and methods from the Minecraft class will be added to the generated classes. If they match any of the mappings, they will be renamed to the mapped name. The
@MappedMethod
annotation will also be added. Otherwise, they will be automatically marked as@Deprecated
. - All generated classes are placed in the
holder
directory within each-mapping
subproject.
- Depending on whether classes are registered with
gradle test -x common:test --rerun-tasks --continue
This simple test finds all constructors and methods annotated with @MappedMethod
and stores the constructor or method class, modifiers, name, and signature into a text file located in the build/mappedMethods
directory.
For constructors and methods not annotated with @MappedMethod
, this test also checks that they are marked as @Deprecated
and final or non-public.
These tests are run for all versions.
Step 4: Verify that all constructors and methods annotated with @MappedMethod
exists across all versions.
gradle common:test --rerun-tasks
This test takes the collected results from the previous step and checks that they are consistent across versions. Essentially, this test checks that something marked with @MappedMethod
also exists in all other versions.
gradle common:build jar remapJar -x test
The gradle jar
and remapJar
commands builds deobfuscated JAR files for Fabric and Forge respectively. Several common classes in the common
subproject, including the @MappedMethod
annotation, get built as well.
After building, JAR files in each version (including common
) are copied over to the build/release
directory. They are also available for download in the Mappings
artifact of GitHub action runs.
More to come soon!
Comma separated values (CSV) files are generated as part of the build pipeline. They contain a list of method names that are unmapped by manually or automatically created mappings for each registered Minecraft class.
These CSV files are located in the build/libraryMethods
directory. They are also available for download in the Minecraft Methods
artifact of GitHub action runs.
When creating a new version, simply create the <mod loader>/<Minecraft version>-generator
and <mod loader>/<Minecraft version>-mapping
directories and register the subprojects in settings.gradle
using the include()
method.
As mentioned above, add entries to ClassScannerTest
for each -generator
subproject. Make sure that each entry exists in all versions and the mapped name is the same between all versions.
To manually add method name mappings, edit MethodMaps
in buildSrc
.
This project is licensed with the MIT License.