Forge is a free, open-source modding API most of your favourite mods use!
This repository contains MinecraftForge 1.4.7 updated to use the modern data-driven toolchain. It works for both developing as well as running mods, and is fully backwards-compatible with original releases.
Get the latest release from maven here.
Mod developers should download the MDK, while users will want the installer.
Download the installer, which will install Forge into your vanilla launcher environment, where you can then create a new profile using that version and play the game.
Download and extract the MDK into a new folder, then follow the instructions in its README
The new data-driver MinecraftForge toolchain allows updating old versions to use modern technologies. This document explains what changes had to be done to get 1.4.7 working and the issues I have encountered.
The used source code originates from the latest available 1.4.7 Forge release version 1.4.7-6.6.2.534
. Minor changes
have been applied to make it compatible with the new toolchain, all of which are described below.
Patches were probably the hardest part to port. The old toolchain uses 2 separate sets of patches - both FML and Forge have their own. While FML patches use SRG names, Forge patches are applied after the source is remapped and therefore use MCP names instead, as well as include javadocs from the mappings. This introduces a couple of issues:
- It is technically impossible to remap MCP patch files to SRG
- Javadocs are not added to code by FG anymore
- Minor hunk line differences caused by code formatting
These issue invalidate the patch hunks' location and context, making porting the patches very hard. I decided to skip automating this process, and instead deobfuscated the FML patches using a python script, then manually applied both sets manually. This process is far from perfect, and there were a few cases where I missed a hunk or applied it in the wrong place.
The project's buildscript was initially based on Forge 1.12, which is the oldest available version that uses FG3. Some parts were later ported from newer versions, namely install-time patching and stable zip times. It also underwent a large overhaul when I converted it to the Kotlin DSL and later split it into separate buildscripts for each subproject to enable Type-safe model accessors.
The originally used MCP version 7.26a
used an old, obfuscated build of FernFlower from around 2012. Thankfully, the
decompiler was later open-sourced 2 years later. It is unclear how different the code was from the older build, but the
two's outputs were similar in comparison.
MinecraftForge already maintains a patched version of FernFlower called ForgeFlower, which is built by applying a set of patches to a specific upstream commit. By rebasing it onto the first working commit I could find in the history of the repo (ddffcf6) and backporting a few future commits as patches, I was able to put together a version whose output closely matched that of the original. However, a few disprenancies still remain - mainly in code style, enums and parameter names.
Maven coordinates: net.minecraftforge:forgeflower-legacy
Repo link: Su5eD/ForgeFlower
Some parts of ForgeGradle are still hardcoded and cannot be changed without modifying FG itself, plus we also need to handle a few special cases. I have only made the minimal changes necessary to the fork, while still keeping it compatible with modern versions.
Old minecraft versions come with embedded libraries which are also obfuscated. In our case, these are Argo - the JSON parser, and BouncyCastle provider - an encryption library. However, MCP never provided patches for their decompiled output. Instead, they were stripped and replaced with their original, non-obfuscated versions from maven.
The SRG mappings contain entries for these libraries so that they can be deobfuscated during setup, but since we're
replacing them later on, we need a to exclude them from being reobfuscated along with minecraft. Fortunately,
SpecialSource accepts an excluded-packages
argument for this purpose. I've implemented a frontend for it
in PatcherExtension
,
from which the values are passed to respective reobf tasks in both forgedev and userdev.
Before around 1.6(?), mods were obfuscated to notch names rather than SRG. While ForgeGradle already has a config option to switch the reobfuscation target to notch, it doesn't apply to userdev deobf dependencies. Those are still hardcoded to map SRG->MCP.
As a simple yet a bit hacky solution we check for the notch
userdev config flag in the dependency deobfuscator and
switch to using MCP->OBF mappings if it's set to true
. We also generate the mappings if they don't exist yet.
Minecraft 1.4.7 uses ASM version 4.0, which supports reading class files up to Java 7. MCPConfig allows us to configure the java version that will be used to run setup functions in ForgeGradle. This needs to be set to at least Java 8 which is required by the functions' tool libraries. However, FG also uses this variable for setting the minecraft recompilation target during userdev setup. That creates a conflict - we need java 8 to run MCP functions, but we also have to target java 7 during recompilation. Recompiling minecraft with java 8 would result in lots of errors from the ASM mod scanner at runtime.
To solve this, we add a new javaRecompileTarget
option to the userdev config that will be prioritized over the
MCPConfig variable for recompilation.
The old MCP toolchain used a different, obfuscated AccessTransformer format that is not suitable for use with ForgeGradle. The difference between the new specification and the old format is mainly in class names and member name separators: while the new format uses canonical names for classes and ** spaces** to separate member descriptors, the old one uses internal class names and dots instead.
For example, the following line in the new format
public net.minecraft.world.World isValid(Lnet/minecraft/util/math/BlockPos;)Z
would translate to the old format like this
public net/minecraft/world/World.isValid(Lnet/minecraft/util/math/BlockPos;)Z
as well as being obfuscated for use in production.
ForgeGradle would require significant modifications to the way it applies dev-time ATs in order to make it compatible with the old format, which is not ideal. Instead, we can use a gradle task to convert and obfuscate access transformers when the project is built. Deobfuscated project dependencies will also have their ATs deobfuscated so that they can be applied at runtime.
Unfortunately, before the introduction of the FMLAT
manifest attribute, there was no guaranteed way of detecting
access transformers without running the mod. Our best bet is searching for files whose name ends with _at.cfg
, a
convention that all mods seem to follow.
ForgeGradle is primarily built for the new FML, which has a different way of locating mod sources. That's why gradle run
tasks are configured to use SourceSet outputs for their runtime classpath, which have classes and resources **
separated** in different folders (build/classes
and build/resources
). FML picks up the mod from the first valid
source, in this case that's build/classes
, resulting in resources being ignored completely. This issue affects all
versions lower than 1.13 which use the old FML.
To replicate FG2 behavior, we replace SourceSet outputs on each run task's classpath with the project jar artifact.
Plugin ID: net.minecraftforge.gradle-legacy
Repo link: Su5eD/ForgeGradle
JarFilter is a new micro-library used by MCPConfig to strip libraries from minecraft after it's renamed, namely Argo and BouncyCastle Provider.
Repo link: Su5eD/JarFilter
LegacyDev is a dev-only library that helps fix a few issues when running the game in a dev environment, such as loading natives or filtering arguments. However, 1.4.7 requires a few additional fixes.
Normally, minecraft would download additional client assets at runtime. However, their download links don't work anymore, and repeatedly trying to fetch the resources will result in flooding your console with errors like this:
java.io.IOException: Server returned HTTP response code: 403 for URL: http://s3.amazonaws.com/MinecraftResources/sound3/ambient/cave/cave1.ogg
ForgeGradle already downloads these assets for us in a hashed format. Their location is accessible at runtime via
the assetDirectory
environment variable. We only need
to extract
and copy them to the resources
folder inside the game directory. With the patch, this is now done automatically when
you launch the game.
By default, mods on the classpath will not be searched for FML Plugins. Instead, they need to be supplied via a system
property. In legacydev, we search classpath entries for the FMLCorePlugin
manifest attribute, and then pass the
results to FML.
Repo link: Su5eD/LegacyDev
MCPConfig is constantly evolving, and as a result its build configuration is not always backwards-compatible with previous versions. For example, the task for dumping static methods was removed and required exceptor files (exceptions, constructors, access) are no longer included in releases. However, it was easy to get these features back from previous commits.
The RetroGradle team has created a handy tool for converting the old MCP data formats to MCPConfig-compatible ones
called MCPConvert. Using it, I was able to get all the required MCPConfig
data, except for static methods, which had to be generated using MCPConfig's dumpStatic
task.
A new step was added that strips libraries shipped with minecraft (Argo and BouncyCastle) using
the JarFilter tool. This is done right after the rename
step on all sides.
Repo link: Su5eD/MCPConfig
I've run into an issue with ForgeGradle's createExc
task where the exceptor didn't include parameter names of methods
that had no SRG mapping. This resulted in the parameters not being renamed and creating unnecessary changes in patches.
I've taken a look at 1.12's parameter mappings and found out they no longer include non-srg methods' parameter names. So
as a solution, I've excluded all non-srg methods' parameters from the 1.4.7 MCP mappings params.csv
as well. Worked
like a charm.
MergeTool is responsible for merging the client and server jars, as well as adding @SideOnly
annotations so sided
classes and members. While the marker annotations usually come from forge, they need to be added separately to vanilla
minecraft environments, specifically the clean
forgedev subproject. For this purpose, a jar with marker-only classes
is published along with mergetool.
To make it compatible with 1.4.7, the jar's compile target was lowered to Java 6, which was the original FML's target java version.
Repo link: Su5eD/MergeTool
Parts of the old source code, mainly the FML relauncher, are not compatible with the way the new toolchain handles setup. Minor modifications must've been done to make it launch properly.
In the first place, the updated toolchain uses install-time binpatches as opposed to the old way of manually copying files into the minecraft jar (don't forget to delete META-INF!). Due to this, a few changes need to be made to handling launch and loading patches at runtime.
The old FML used Minecraft's own entry points which were already patched in the jar and would immediately redirect to FML so that it could perform setup before returning to Minecraft.
However, patched classes generated by the installer are packaged as a separate jar that we need to locate at runtime and inject it onto the classpath. For this, we have to create our own entry points for each side that will run FML setup instead of Minecraft's ones.
LibraryFinder
is a new class inspired by the 1.19
Forge equivalent.
It's responsible for locating installer output libraries and adding them onto the classpath. The RelaunchClassLoader
has been modified to include a "child" URLClassLoader
that wraps the located libraries and is used for priority
loading of resources.
Made the minecraft home computation method use the current directory as a fallback if
the minecraft.applet.TargetDirectory
system property is missing.
ModContainer
contains an isMinecraft
field which is used by FML to exclude itself from being warned about classes
in net.minecraft.src
. ModDiscoverer
previously relied on the assumption that the jar containing minecraft classes
was always first on the classpath. However, this doesn't apply to IDE runs which put JRE jars first.
As a workaround, I've replaced the logic with a method that looks for the net/minecraft/server/MinecraftServer.class
jar entry (or file, if the mod is a directory).
The ForgeVersion
class has been patched to fetch version data from forgeversion.properties
instead of being
hardcoded.
SpecialSource already supported 2 of the features I needed, though I found out they were broken.
The excluded-packages
argument allows excluding certain packages from being reobfuscated, and it's applied in the
mappings parser rather than the processing stage. As I found out, this functionality
was missing from the T/CSRG parser method, although I was able to fix
it by reusing existing code from the SRG parser.
The SRG mappings format allows defining mappings for entire packages as a fallback if no class mapping is present. The
old FML used this feature to remap all unmapped classes in the default package to net.minecraft.src
, specifically
ModLoader classes.
Due to a bug, package renaming doesn't currently work in reverse. However, I was able to easily fix it by reusing existing handler code from the SRG parser again.
During installation, we need to deobfuscate minecraft's embedded libraries without touching the rest of the classes. To do that, we need a way of restricting which classes we are mapping to rather than from.
I decided to implement this as a new feature to SpecialSource under the remap-only
argument. It's applied as a filter
in the mappings parser just like exclude-packages
, except it acts as a whitelist rather than blacklist, filtering
by mapped names instead of unmapped ones.
Repo link: Su5eD/SpecialSource
Argo is rehosted on my maven because MavenCentral's version has an invalid POM that breaks Gradle.