| Line 7: |
Line 7: |
| | | | |
| | == Overall process == | | == Overall process == |
| − | When setting up the environment for the first time, a gradle refresh triggers three things:
| + | Whenever a gradle refresh is triggered, a few things occur: |
| − | # ForgeGradle downloads the MCPConfig zip for the file you're using, and triggers the extractSrg/createSrgToMcp task. | + | # ForgeGradle downloads the jar and MCPConfig zip and triggers the extractSrg/createSrgToMcp task. |
| − | # After that, it processes the jar - applies access transformers, side stripper and others | + | # After that, it processes the jar - applies access transformers from forge/userdev, SAS from forge, and decompiles. |
| | # Finally, it patches and finalizes the code, ready for modder consumption. | | # Finally, it patches and finalizes the code, ready for modder consumption. |
| | + | |
| | + | This is triggered whenever the <code>setup</code> task is ran. |
| | | | |
| | == Needed knowledge == | | == Needed knowledge == |
| | === Obfuscation === | | === Obfuscation === |
| − | '''Obfuscation''' is the process of renaming all of the fields, methods, and classes of the compiled code into unreadable, machine-generated names (such as <code>aaa</code>, <code>bC</code>), and removing package structures (this makes everything package-local and makes the obfuscated code smaller somewhat. This is commonly used by companies to prevent external entities from easily decompiling their released binaries/executables and retrieving their source code/intellectual property, though it does have size advantages. | + | '''Obfuscation''' is the process of renaming all of the fields, methods, and classes of the compiled code into unreadable, machine-generated names (such as <code>aaa</code>, <code>bC</code>), and removing package structures (this makes everything package-local and makes the obfuscated code smaller somewhat. This is commonly used by companies to prevent external entities from easily decompiling their released binaries/executables and retrieving their source code/intellectual property. |
| | | | |
| | Additionally, due to the way the Local Variable Table (LVT) of Java bytecode is stored, every function-local variable name is turned to <code>☃</code> (that's right, a snowman) in the compiled files. This makes immediate recompilation of the game literally and physically impossible, as every Java compiler currently available requires that local variables have unique names. | | Additionally, due to the way the Local Variable Table (LVT) of Java bytecode is stored, every function-local variable name is turned to <code>☃</code> (that's right, a snowman) in the compiled files. This makes immediate recompilation of the game literally and physically impossible, as every Java compiler currently available requires that local variables have unique names. |
| Line 25: |
Line 27: |
| | # It is incredibly difficult to create mods using these obfuscated names. It requires immense patience to reverse-engineer the meanings behind each and every name, and to keep relating those names to what was already reverse-engineered. Although, tools do exist to make this process easier, such as IntelliJ IDEA plugins that provide naming hints automatically. | | # It is incredibly difficult to create mods using these obfuscated names. It requires immense patience to reverse-engineer the meanings behind each and every name, and to keep relating those names to what was already reverse-engineered. Although, tools do exist to make this process easier, such as IntelliJ IDEA plugins that provide naming hints automatically. |
| | # Because the obfuscation process takes place after compilation (the obfuscator operates on the compiled classes), the obfuscated names are not handled by the compiler. Thus, obfuscated classes may contain member<ref>'''member''' refers to class fields and methods.</ref> names that are invalid in the Java source language, but valid in compiled bytecode (like ☃ discussed earlier); this means that the decompiled source of the game is not immediately recompilable. | | # Because the obfuscation process takes place after compilation (the obfuscator operates on the compiled classes), the obfuscated names are not handled by the compiler. Thus, obfuscated classes may contain member<ref>'''member''' refers to class fields and methods.</ref> names that are invalid in the Java source language, but valid in compiled bytecode (like ☃ discussed earlier); this means that the decompiled source of the game is not immediately recompilable. |
| − | # These obfuscated names are automatically generated by the obfuscator for each independent release. This means that the obfuscated names may change signficantly between any two versions, making it harder for mod developers to update mods between releases. | + | # These obfuscated names are automatically generated by the obfuscator for each independent release. This means that the obfuscated names may change significantly between any two versions, making it harder for mod developers to update mods between releases. |
| | | | |
| | === SRGification === | | === SRGification === |
| Line 35: |
Line 37: |
| | The SRG name of the member is then derived from its SRG ID and its type (method {given the prefix <code>m_</code>}, field {given the prefix <code>f_</code>}, or parameter {given the prefix <code>p_</code>}). <ref>The SRG name for a given member is only created once, when it first appears in the code.</ref>. This inclusion of the SRG ID into the name guarantees that the SRG name for all members are unique, and is the reason the ID is generated. | | The SRG name of the member is then derived from its SRG ID and its type (method {given the prefix <code>m_</code>}, field {given the prefix <code>f_</code>}, or parameter {given the prefix <code>p_</code>}). <ref>The SRG name for a given member is only created once, when it first appears in the code.</ref>. This inclusion of the SRG ID into the name guarantees that the SRG name for all members are unique, and is the reason the ID is generated. |
| | | | |
| − | The actual conversion of obf names to SRG names is done by a tool called [[Toolchain:Vignette|Vignette]]. More information on how it works can be found on that page. | + | The actual conversion of obf names to SRG names is done by a tool called [[Toolchain#Vignette|Vignette]]. More information on how it works can be found on that page. |
| | | | |
| | == The Setup == | | == The Setup == |
| | The process can be broken up into 3 steps; MCPConfig, patch and provide. | | The process can be broken up into 3 steps; MCPConfig, patch and provide. |
| | The MCPConfig step is, understandably, the biggest and most prone to failure. | | The MCPConfig step is, understandably, the biggest and most prone to failure. |
| − | An explanation of MCPConfig itself, how it works, what it's for (but NOT how to use it) can be found [[Toolchain:MCPConfig|here]]. For the purpose of this guide, you need only know that its' goal is to get the game decompiled, and into a state where it can immediately be recompiled. Due to certain flaws in the rest of the toolchain, this means it needs to fix and patch the source code before passing it onto Forge. | + | An explanation of MCPConfig itself, how it works, what it's for (but NOT how to use it) can be found [[Toolchain:MCPConfig|here]]. For the purpose of this guide, you need only know that its' goal is to get the game decompiled, and into a state where it can immediately be recompiled. This means it needs to fix and patch the source code before passing it onto Forge. |
| | | | |
| | In this way, MCPConfig can be thought of the vanilla side of the setup. It does not modify the game. | | In this way, MCPConfig can be thought of the vanilla side of the setup. It does not modify the game. |
| Line 60: |
Line 62: |
| | ** java -jar <version> <args> <jvmArgs> | | ** java -jar <version> <args> <jvmArgs> |
| | | | |
| − | An example config.json can be found [https://github.com/MinecraftForge/MCPConfig/blob/master/versions/release/1.17.1/config.json here].
| + | {{Tip|content=A config.json can be found [https://github.com/MinecraftForge/MCPConfig/blob/master/versions/release/1.17.1/config.json here] which defines the following steps: |
| − | | |
| − | It defines the steps:
| |
| | * [[Toolchain:ForgeFlower|fernflower]] | | * [[Toolchain:ForgeFlower|fernflower]] |
| | ** version: net.minecraftforge:forgeflower:1.5.498.12 | | ** version: net.minecraftforge:forgeflower:1.5.498.12 |
| Line 74: |
Line 74: |
| | ** args: --jar-in {input} --jar-out {output} --mapping-format tsrg2 --mappings {mappings} --fernflower-meta --cfg {libraries} --create-inits --fix-param-annotations | | ** args: --jar-in {input} --jar-out {output} --mapping-format tsrg2 --mappings {mappings} --fernflower-meta --cfg {libraries} --create-inits --fix-param-annotations |
| | | | |
| − | More information about each of these tools can be found at the link provided, as well as what each of these arguments do. A brief description is provided. | + | More information about each of these tools can be found at the link provided, as well as what each of these arguments do. A brief description is provided.}} |
| − | | |
| − | = TODO UPDATE BELOW =
| |
| | | | |
| | === ForgeFlower === | | === ForgeFlower === |
| − | After the code has been cleaned up by MCInjector, to a state where it no longer conflicts with itself, it can be passed to the decompiler.
| + | The decompiler used by ForgeGradle is a custom fork of [https://github.com/JetBrains/intellij-community/tree/master/plugins/java-decompiler/engine Jetbrains' FernFlower], called [https://github.com/MinecraftForge/ForgeFlower ForgeFlower] which searches the jar for files, cleans up the bytecode, and then converts it into a reasonable best-guess interpretation. |
| − | | |
| − | The decompiler used by ForgeGradle is a custom fork of [https://github.com/JetBrains/intellij-community/tree/master/plugins/java-decompiler/engine Jetbrains' FernFlower], called [https://github.com/MinecraftForge/ForgeFlower ForgeFlower]. | |
| | | | |
| − | It simply searches the jar for files, cleans up the bytecode, and then converts it into a reasonable best-guess interpretation. | + | It also produces the following side effects: |
| − | | |
| − | It:
| |
| | * Removes synthetic parameters from constructors | | * Removes synthetic parameters from constructors |
| | ** In bytecode, inner classes have the outer class as their first constructor parameter, but Java source code does not. | | ** In bytecode, inner classes have the outer class as their first constructor parameter, but Java source code does not. |
| Line 105: |
Line 99: |
| | | | |
| | === Mergetool === | | === Mergetool === |
| − | The game is split into two distributions; server and client. | + | The game is split into two distributions; server and client. Since the server is just a subset of the client, the client contains the server-only classes as well. |
| − | | |
| − | Because the server contains no rendering code, and the client contains none of the server-specific code (like the UI), this means there are differences between what can run on one side or the other.
| |
| | | | |
| − | To get around this, we have a tool called Mergetool, which can search for the differences between two files (down to the function level) and merge them into one large (referred to as joined) jar file. | + | To get around this, we have a tool called Mergetool, which can search for the differences between two files (down to the function level) and merge them into one large (referred to as joined) jar file. It also can also annotate those files as necessary. |
| | | | |
| | It is a simple program, but it works. | | It is a simple program, but it works. |
| Line 116: |
Line 108: |
| | Vignette is where SRG starts to come into play. It serves the role of our deobfuscator, performing deobfuscation. | | Vignette is where SRG starts to come into play. It serves the role of our deobfuscator, performing deobfuscation. |
| | | | |
| − | This process is done with the help of a '''deobfusation map''', a file generated by the original obfuscator (in this case, ProGuard) that contains a map of the obfuscated names to original, non-obfuscated names. This is commonly used on debofuscating stack traces outputted by an obfuscated program, for debugging purposes.<ref name="retrace"/> | + | This process is done with the help of a '''deobfusation map''', a file generated by the original obfuscator (in this case, ProGuard) that contains a map of the obfuscated names to original, non-obfuscated names. This is commonly used on deobfuscating stack traces outputted by an obfuscated program, for debugging purposes.<ref name="retrace"/> |
| | | | |
| | We have three sets of deobfuscation maps available to us; the obf->SRG mappings distributed with the MCPConfig system, the Yarn intermediary system, or the official mappings.<ref name="mojmappings"/>. | | We have three sets of deobfuscation maps available to us; the obf->SRG mappings distributed with the MCPConfig system, the Yarn intermediary system, or the official mappings.<ref name="mojmappings"/>. |
| Line 135: |
Line 127: |
| | | | |
| | A recap from earlier: | | A recap from earlier: |
| − | * For classes -> <code>c_###_</code> (but it is immediately changed without the c_ being written to disk) | + | * For classes -> <code>C_###_</code> (but it is transparently remapped without the C_ being written to disk) |
| | * For functions/methods -> <code>m_<ID>_</code> | | * For functions/methods -> <code>m_<ID>_</code> |
| | * For fields -> <code>f_<ID>_</code> | | * For fields -> <code>f_<ID>_</code> |
| Line 146: |
Line 138: |
| | === Patches === | | === Patches === |
| | Once we have the source code ready to go, the final step in the setup is to apply patches. | | Once we have the source code ready to go, the final step in the setup is to apply patches. |
| − | These are done trivially, using diffs and [https://github.com/MinecraftForge/BinaryPatcher BinaryPatcher]. | + | These are done trivially using [https://github.com/MinecraftForge/DiffPatch DiffPatch]. |
| | | | |
| | == The Forge Side == | | == The Forge Side == |
| | | | |
| − | After the MCPConfig/SetupMCP tasks are finished, ForgeGradle will print '''MCP Environment Setup is complete''' and wait for the user input.
| + | When Forge is first loaded by gradle, it prepares the files for setting up the workspace. |
| | | | |
| − | The next step is to run the <code>gradlew setup</code> task, which does what it implies: Applying the Forge system to the processed vanilla code. | + | The initial setup is done through the <code>gradlew setup</code> task. It will run the SetupMCP tasks and then start applying the Forge system to the processed vanilla code. After the MCPConfig/SetupMCP tasks are finished, ForgeGradle will print '''MCP Environment Setup is complete''' and continue. The text '''pausing after requested step''' is also printed twice during this task such that some necessary data can be extracted. |
| | | | |
| − | First, it applies ATs. After that, patches. Finally, the official mappings are applied. | + | First, it applies ATs. After that, patches. Finally, the official mappings, or some other mappings from a provider like Parchment, are applied. |
| | | | |
| | All in all, compared to the MCPConfig setup, this is a string of extremely basic tasks - mostly just one-line commands. | | All in all, compared to the MCPConfig setup, this is a string of extremely basic tasks - mostly just one-line commands. |
| Line 165: |
Line 157: |
| | * <code>AccessTransformers.jar --inJar {input} --outJar {output} --logFile accesstransform.log --atFile {at.cfg}</code> | | * <code>AccessTransformers.jar --inJar {input} --outJar {output} --logFile accesstransform.log --atFile {at.cfg}</code> |
| | | | |
| − | [https://github.com/MinecraftForge/MinecraftForge/blob/1.17.x/src/main/resources/META-INF/accesstransformer.cfg The current AT cfg can be found here.] | + | [https://github.com/MinecraftForge/MinecraftForge/blob/1.17.x/src/main/resources/META-INF/accesstransformer.cfg The AT cfg for Forge can be found here.] |
| | | | |
| | === Applying Patches === | | === Applying Patches === |
| Line 171: |
Line 163: |
| | The patches used for Forge itself are different from those used by MCPConfig, which means there are two separate patching stages performed. | | The patches used for Forge itself are different from those used by MCPConfig, which means there are two separate patching stages performed. |
| | | | |
| − | As opposed to the minimal MCPConfig patching, with the goal to make the code recompilable, the Forge patching is done to apply the API and modloader to the code. | + | As opposed to the minimal MCPConfig patching, with the goal to make the code recompilable, the Forge patching is done to apply the API and mod loader to the code. |
| | | | |
| − | It does this in a very similar way, with the [https://github.com/MinecraftForge/BinaryPatcher BinaryPatcher] utility. | + | It does this in a very similar way, with the [https://github.com/MinecraftForge/BinaryPatcher BinaryPatcher] utility used at install time/during a CI build or [https://github.com/MinecraftForge/DiffPatch DiffPatch] in all other situations. |
| | | | |
| | | | |
| Line 191: |
Line 183: |
| | There is not much left to do but package the code into a jar file, and place it into the gradle cache (so that this process does not occur every single time the project is opened). It calculates this name based on many factors, and this is covered in the [[ForgeGradle#naming|naming]] article. | | There is not much left to do but package the code into a jar file, and place it into the gradle cache (so that this process does not occur every single time the project is opened). It calculates this name based on many factors, and this is covered in the [[ForgeGradle#naming|naming]] article. |
| | | | |
| − | == Some additional Infos == | + | == Some Additional Info == |
| − | # You will never see c_XXX_ for classes, because the class names are picked beforehand using the official mapping classnames | + | # You will never see C_XXX_ for classes, because the class names are picked beforehand using the official mapping class names |
| | # If you look into the JAR, you won't see any packages for the obfuscated classes, but the deobfuscated classes do have the packages, this is because the same process that names the classes, also decides what package they belong to | | # If you look into the JAR, you won't see any packages for the obfuscated classes, but the deobfuscated classes do have the packages, this is because the same process that names the classes, also decides what package they belong to |
| | | | |