├── .gitignore ├── .gitmodules ├── CC0-LICENSE ├── LICENSE ├── MCP-LICENSE ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src └── main └── kotlin ├── SpigotMappingType.kt ├── main.kt ├── provider ├── intermediary.kt ├── mcp.kt ├── mojang.kt ├── spigot.kt └── yarn.kt ├── tiny └── tiny.kt ├── tsrgUtil.kt └── version.kt /.gitignore: -------------------------------------------------------------------------------- 1 | mappings/ 2 | bin 3 | cache 4 | .idea 5 | *.iml 6 | __pycache__ 7 | .atom_jvm_classpath 8 | build/ 9 | .gradle/ 10 | *.zip 11 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "MCPConfig"] 2 | path = MCPConfig 3 | url = https://github.com/MinecraftForge/MCPConfig.git 4 | [submodule "yarn"] 5 | path = yarn 6 | url = https://github.com/FabricMC/yarn.git 7 | -------------------------------------------------------------------------------- /CC0-LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Jadon Fowler 4 | Copyright (c) contributors 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. -------------------------------------------------------------------------------- /MCP-LICENSE: -------------------------------------------------------------------------------- 1 | License and terms of use. 2 | ========================= 3 | 4 | No warranties. If MCP does not work for you, or causes any damage, it's your problem. Use it at own risk. 5 | 6 | You are allowed to: 7 | - Use MCP to decompile the Minecraft client and server jar files. 8 | - Use the decompiled source code to create mods for Minecraft. 9 | - Recompile modified versions of Minecraft. 10 | - Reobfuscate the classes of your mod for Minecraft. 11 | 12 | You are NOT allowed to: 13 | - Use MCP to do anything that violated Mojangs terms of use for Minecraft. 14 | - Release Minecraft versions or modifications that allow you to play without having bought Minecraft from Mojang. 15 | - Release modified or unmodified versions of MCP anywhere. 16 | - Use any of MCPs scripts, tools or data files without explicit written permission. 17 | - Make money with anything based on MCP (excluding Minecraft mods created by using MCP). 18 | - Use MCP to create clients that are used for griefing or exploiting server bugs. 19 | - Release the decompiled source code of Minecraft in any way. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Minecraft Mappings 2 | 3 | This "tool" creates transitive mappings for various Minecraft versions. 4 | 5 | All the mappings can be translated to one another for a given version: `spigot2mcp.srg`, `srg2obf.mcp`, etc. 6 | 7 | Supported versions: 8 | 9 | | | Spigot | MCP | Searge | Yarn | Intermediary | Mojang | 10 | |--------|----------|----------|-----------|----------|---------------|----------| 11 | | 1.20.2 | ✓ | | ✓ | ✓ | ✓ | ✓ | 12 | | 1.20.1 | ✓ | | ✓ | ✓ | ✓ | ✓ | 13 | | 1.19.4 | ✓ | | ✓ | ✓ | ✓ | ✓ | 14 | | 1.19.3 | ✓ | | ✓ | ✓ | ✓ | ✓ | 15 | | 1.19.2 | ✓ | | ✓ | ✓ | ✓ | ✓ | 16 | | 1.18.2 | ✓ | | ✓ | ✓ | ✓ | ✓ | 17 | | 1.16.5 | ✓ | | ✓ | ✓ | ✓ | ✓ | 18 | | 1.16.4 | ✓ | | ✓ | ✓ | ✓ | ✓ | 19 | | 1.16.3 | ✓ | | ✓ | ✓ | ✓ | ✓ | 20 | | 1.16.2 | ✓ | | ✓ | ✓ | ✓ | ✓ | 21 | | 1.16.1 | ✓ | | ✓ | ✓ | ✓ | ✓ | 22 | | 1.16 | ✓ | | ✓ | ✓ | ✓ | ✓ | 23 | | 1.15.2 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 24 | | 1.15.1 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 25 | | 1.15 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 26 | | 1.14.4 | ✓ | ✓ | ✓ | ✓ | ✓ | 27 | | 1.14.3 | ✓ | ✓ | ✓ | ✓ | ✓ | 28 | | 1.14.2 | ✓ | ✓ | ✓ | ✓ | ✓ | 29 | | 1.14.1 | ✓ | ✓ | ✓ | ✓ | ✓ | 30 | | 1.14 | ✓ | ✓ | ✓ | ✓ | ✓ | 31 | | 1.13.2 | ✓ | ✓ | ✓ | | | 32 | | 1.13.1 | ✓ | ✓ | ✓ | | | 33 | | 1.13 | ✓ | ✓ | ✓ | | | 34 | | 1.12.2 | ✓ | ✓ | ✓ | | | 35 | | 1.12 | ✓ | ✓ | ✓ | | | 36 | | 1.11 | ✓ | ✓ | ✓ | | | 37 | | 1.10.2 | ✓ | ✓ | ✓ | | | 38 | | 1.9.4 | ✓ | ✓ | ✓ | | | 39 | | 1.9 | ✓ | ✓ | ✓ | | | 40 | | 1.8.8 | ✓ | ✓ | ✓ | | | 41 | | 1.8 | ✓ | ✓ | ✓ | | | 42 | | 1.7.10 | | ✓ | ✓ | | | 43 | 44 | Supported formats: 45 | 46 | - SRG 47 | - CSRG 48 | - TSRG 49 | - Tiny (fields descriptors don't exist) 50 | - JSON 51 | 52 | These mappings were made possible by @Techcable, the MCP team, Bukkit, SpigotMC, and various other people. 53 | 54 | ## TODO 55 | 56 | - [ ] [older versions](https://github.com/agaricusb/MinecraftRemapping) 57 | - [ ] < 1.8 CraftBukkit mappings? 58 | - [ ] snapshots? 59 | 60 | ## License 61 | 62 | * All Kotlin scripts are MIT Licensed. 63 | 64 | * The MCP mappings are the property of the MCP Team and are released under the MCP License. 65 | 66 | * The Spigot mappings are copyright SpigotMC Pty. Ltd. 67 | 68 | * The Yarn mappings are licensed under the Creative Commons Zero license. 69 | 70 | * The Mojang mappings are copyright Microsoft. 71 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'org.jetbrains.kotlin.jvm' version '1.9.22' 4 | id 'application' 5 | } 6 | 7 | application { 8 | mainClassName = "MainKt" 9 | } 10 | 11 | group 'io.jadon' 12 | version '1.0-SNAPSHOT' 13 | 14 | repositories { 15 | mavenLocal() 16 | mavenCentral() 17 | maven { 18 | url 'https://maven.modmuss50.me' 19 | } 20 | maven { 21 | url 'https://maven.fabricmc.net' 22 | } 23 | maven { url 'https://jitpack.io' } 24 | } 25 | 26 | dependencies { 27 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" 28 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4' 29 | 30 | implementation "com.github.phase:SrgLib:master-SNAPSHOT" 31 | implementation 'com.opencsv:opencsv:3.9' 32 | implementation 'com.google.code.gson:gson:2.9.0' 33 | implementation 'com.google.guava:guava:33.0.0-jre' 34 | implementation 'net.md-5:SpecialSource:1.11.0' 35 | implementation 'org.cadixdev:lorenz:0.5.8' 36 | implementation 'org.cadixdev:lorenz-io-proguard:0.5.8' 37 | implementation 'cuchaz:enigma:0.13.1.+:all' 38 | } 39 | 40 | compileKotlin { 41 | kotlinOptions.jvmTarget = "1.8" 42 | } 43 | compileTestKotlin { 44 | kotlinOptions.jvmTarget = "1.8" 45 | } 46 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MohistMC/MinecraftMappings/109af0c088ab71fdbb0a1a771497203eddcfd99f/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 84 | 85 | APP_NAME="Gradle" 86 | APP_BASE_NAME=${0##*/} 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | MAX_FD=$( ulimit -H -n ) || 147 | warn "Could not query maximum file descriptor limit" 148 | esac 149 | case $MAX_FD in #( 150 | '' | soft) :;; #( 151 | *) 152 | ulimit -n "$MAX_FD" || 153 | warn "Could not set maximum file descriptor limit to $MAX_FD" 154 | esac 155 | fi 156 | 157 | # Collect all arguments for the java command, stacking in reverse order: 158 | # * args from the command line 159 | # * the main class name 160 | # * -classpath 161 | # * -D...appname settings 162 | # * --module-path (only if needed) 163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 164 | 165 | # For Cygwin or MSYS, switch paths to Windows format before running java 166 | if "$cygwin" || "$msys" ; then 167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 169 | 170 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 171 | 172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 173 | for arg do 174 | if 175 | case $arg in #( 176 | -*) false ;; # don't mess with options #( 177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 178 | [ -e "$t" ] ;; #( 179 | *) false ;; 180 | esac 181 | then 182 | arg=$( cygpath --path --ignore --mixed "$arg" ) 183 | fi 184 | # Roll the args list around exactly as many times as the number of 185 | # args, so each arg winds up back in the position where it started, but 186 | # possibly modified. 187 | # 188 | # NB: a `for` loop captures its iteration list before it begins, so 189 | # changing the positional parameters here affects neither the number of 190 | # iterations, nor the values presented in `arg`. 191 | shift # remove old arg 192 | set -- "$@" "$arg" # push replacement arg 193 | done 194 | fi 195 | 196 | # Collect all arguments for the java command; 197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 198 | # shell script including quotes and variable substitutions, so put them in 199 | # double quotes to make sure that they get re-expanded; and 200 | # * put everything else in single quotes, so that it's not re-expanded. 201 | 202 | set -- \ 203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 204 | -classpath "$CLASSPATH" \ 205 | org.gradle.wrapper.GradleWrapperMain \ 206 | "$@" 207 | 208 | # Stop when "xargs" is not available. 209 | if ! command -v xargs >/dev/null 2>&1 210 | then 211 | die "xargs is not available" 212 | fi 213 | 214 | # Use "xargs" to parse quoted args. 215 | # 216 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 217 | # 218 | # In Bash we could simply go: 219 | # 220 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 221 | # set -- "${ARGS[@]}" "$@" 222 | # 223 | # but POSIX shell has neither arrays nor command substitution, so instead we 224 | # post-process each arg (as a line of input to sed) to backslash-escape any 225 | # character that might be a shell metacharacter, then use eval to reverse 226 | # that process (while maintaining the separation between arguments), and wrap 227 | # the whole thing up as a single "set" statement. 228 | # 229 | # This will of course break if any of these variables contains a newline or 230 | # an unmatched quote. 231 | # 232 | 233 | eval "set -- $( 234 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 235 | xargs -n1 | 236 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 237 | tr '\n' ' ' 238 | )" '"$@"' 239 | 240 | exec "$JAVACMD" "$@" 241 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if %ERRORLEVEL% equ 0 goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if %ERRORLEVEL% equ 0 goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | set EXIT_CODE=%ERRORLEVEL% 84 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 85 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 86 | exit /b %EXIT_CODE% 87 | 88 | :mainEnd 89 | if "%OS%"=="Windows_NT" endlocal 90 | 91 | :omega 92 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'mapping-generator' 2 | 3 | -------------------------------------------------------------------------------- /src/main/kotlin/SpigotMappingType.kt: -------------------------------------------------------------------------------- 1 | enum class SpigotMappingType { 2 | 3 | NO_SPIGOT, 4 | SPIGOT, 5 | MODERN_SPIGOT 6 | 7 | } -------------------------------------------------------------------------------- /src/main/kotlin/main.kt: -------------------------------------------------------------------------------- 1 | import java.io.File 2 | 3 | val GLOBAL_FOLDER = File("mappings") 4 | 5 | fun main() { 6 | val time = System.currentTimeMillis() 7 | GLOBAL_FOLDER.mkdirs() 8 | MinecraftVersion.V1_20_1.write(GLOBAL_FOLDER) 9 | val elapsed = (System.currentTimeMillis() - time) / 1000.0 10 | println("Done. Took ${elapsed / 60}m (${elapsed}s)") 11 | } 12 | -------------------------------------------------------------------------------- /src/main/kotlin/provider/intermediary.kt: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import cuchaz.enigma.ProgressListener 4 | import cuchaz.enigma.translation.mapping.serde.MappingFormat 5 | import net.techcable.srglib.format.MappingsFormat 6 | import net.techcable.srglib.mappings.Mappings 7 | import java.io.File 8 | import java.net.URL 9 | import java.util.concurrent.TimeUnit 10 | import java.util.zip.GZIPInputStream 11 | 12 | 13 | fun getLegacyIntermediaryVersion(minecraftVersion: String): String { 14 | return minecraftVersion 15 | } 16 | 17 | fun downloadLegacyIntermediary(yarnVersion: String, file: File) { 18 | try { 19 | URL("https://raw.githubusercontent.com/Legacy-Fabric/Legacy-Intermediaries/master/mappings/$yarnVersion.tiny").downloadTo(file) 20 | } catch (ex : Exception) { 21 | file.delete() 22 | URL("https://raw.githubusercontent.com/IsaiahPatton/Legacy-Intermediaries/master/mappings/$yarnVersion.tiny").downloadTo(file) 23 | } 24 | } 25 | 26 | fun getLegacyIntermediaryMappings(minecraftVersion: String): Map { 27 | val yarnMavenVersion = getLegacyIntermediaryVersion(minecraftVersion) 28 | val yarnZip = File("cache/legacyIntermediary-$yarnMavenVersion.gz") 29 | if (!yarnZip.exists()) { 30 | downloadLegacyIntermediary(yarnMavenVersion, yarnZip) 31 | } 32 | val tinyMappings = tiny.Mappings() 33 | //GZIPInputStream(yarnZip.inputStream()).use { zip -> 34 | var namespaces = listOf() 35 | yarnZip.readLines().forEach { line -> 36 | val parts = line.split("\t") 37 | when (true) { 38 | line.startsWith("v1") -> { 39 | namespaces = parts.subList(2, parts.size) 40 | tinyMappings.namespaces = namespaces.toMutableList() 41 | } 42 | line.startsWith("CLASS\t") -> { 43 | val clazz = tinyMappings.getClass(parts[1]) 44 | parts.forEachIndexed { index, mapped -> 45 | if (index >= 2) { 46 | val namespace = namespaces[index - 2] 47 | if (namespace != "official") { 48 | clazz.add(namespace, mapped) 49 | } 50 | } 51 | } 52 | } 53 | line.startsWith("FIELD\t") -> { 54 | val field = tinyMappings.getField(parts[1], parts[3], parts[2]) 55 | parts.forEachIndexed { index, mapped -> 56 | if (index >= 4) { 57 | val namespace = namespaces[index - 4] 58 | if (namespace != "official") { 59 | field.add(namespace, mapped) 60 | } 61 | } 62 | } 63 | } 64 | line.startsWith("METHOD\t") -> { 65 | val method = tinyMappings.getMethod(parts[1], parts[3], parts[2]) 66 | parts.forEachIndexed { index, mapped -> 67 | if (index >= 4) { 68 | val namespace = namespaces[index - 4] 69 | if (namespace != "official") { 70 | try { 71 | method.add(namespace, mapped) 72 | } catch (e: Exception) { 73 | e.printStackTrace() 74 | println(line) 75 | } 76 | } 77 | } 78 | } 79 | } 80 | 81 | true -> TODO() 82 | false -> TODO() 83 | } 84 | } 85 | //} 86 | tinyMappings.toMappings().forEach { namespace, mappings -> 87 | println("yarn $minecraftVersion $namespace: parsed class=${mappings.classes().size} method=${mappings.fields().size} field=${mappings.methods().size}") 88 | } 89 | return tinyMappings.toMappings() 90 | } 91 | 92 | fun getLegacyIntermediarynMappingsFromSubmodule(minecraftVersion: String): Mappings { 93 | val yarnFolder = File("yarn") 94 | val mappingsFile = File("cache/yarn-$minecraftVersion.srg") 95 | if (mappingsFile.exists()) { 96 | println("yarn $minecraftVersion: yarn-$minecraftVersion.srg already exists") 97 | return MappingsFormat.SEARGE_FORMAT.parseFile(mappingsFile) 98 | } 99 | 100 | println("yarn $minecraftVersion: checking out branch $minecraftVersion") 101 | "git checkout $minecraftVersion".runCommand(yarnFolder) 102 | 103 | println("yarn $minecraftVersion: reading mappings directory") 104 | val entryTree = MappingFormat.ENIGMA_DIRECTORY.read(File(yarnFolder, "mappings").toPath(), ProgressListener.VOID) 105 | println("yarn $minecraftVersion: writing mappings to srg") 106 | MappingFormat.SRG_FILE.write(entryTree, mappingsFile.toPath(), ProgressListener.VOID) 107 | 108 | // TODO: fix these 109 | val brokenClasses = listOf( 110 | "", "WoodlandMansionGenerator", 111 | "VoxelSet", "ParticleManager", "PointOfInterestDebugRenderer", "NumberRange", "ServerLightingProvider", 112 | "SpellcastingIllagerEntity", "NetherFortressGenerator", "TextureUtil" 113 | ) 114 | mappingsFile.writeText(mappingsFile.readLines().filter { 115 | brokenClasses.map { b -> !it.contains(b) }.foldRight(true) { a, b -> a && b } 116 | }.joinToString("\n")) 117 | 118 | return MappingsFormat.SEARGE_FORMAT.parseFile(mappingsFile) 119 | } -------------------------------------------------------------------------------- /src/main/kotlin/provider/mcp.kt: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import TSrgUtil 4 | import com.google.gson.Gson 5 | import com.google.gson.JsonObject 6 | import com.google.gson.stream.JsonReader 7 | import com.opencsv.CSVReader 8 | import net.techcable.srglib.FieldData 9 | import net.techcable.srglib.JavaType 10 | import net.techcable.srglib.MethodData 11 | import net.techcable.srglib.format.MappingsFormat 12 | import net.techcable.srglib.mappings.Mappings 13 | import java.io.File 14 | import java.io.IOException 15 | import java.io.InputStream 16 | import java.net.URL 17 | import java.util.* 18 | import java.util.zip.ZipInputStream 19 | import kotlin.system.exitProcess 20 | 21 | fun downloadMcpMappings(srgMappings: Mappings, mappingsVersion: String): Mappings { 22 | val cacheFile = File("cache/mcp_$mappingsVersion/mcp.json") 23 | val fieldNames = HashMap() 24 | val methodNames = HashMap() 25 | if (!cacheFile.exists()) { 26 | cacheFile.parentFile.mkdirs() 27 | check(cacheFile.createNewFile()) 28 | // Validate and compute the mapping version information 29 | val mappingsVersions: Map>> = 30 | JsonReader(URL("http://export.mcpbot.bspk.rs/versions.json").openStream().reader()).use { reader -> 31 | val result = HashMap>>() 32 | // We have to parse this by hand or else things don't work 33 | reader.beginObject() 34 | while (reader.hasNext()) { 35 | val version = reader.nextName() 36 | val byChannel = HashMap>() 37 | reader.beginObject() 38 | while (reader.hasNext()) { 39 | val channelName = reader.nextName() 40 | val channelVersions = ArrayList() 41 | reader.beginArray() 42 | while (reader.hasNext()) { 43 | channelVersions.add(reader.nextInt()) 44 | } 45 | reader.endArray() 46 | byChannel[channelName] = channelVersions 47 | } 48 | reader.endObject() 49 | result[version] = byChannel 50 | } 51 | reader.endObject() 52 | result 53 | } 54 | val mappingsChannel = mappingsVersion.substring(0, mappingsVersion.indexOf('_')) 55 | val isNodoc = "_nodoc_" in mappingsVersion 56 | val fullMappingsChannel = if (isNodoc) mappingsChannel + "_nodoc" else mappingsChannel 57 | val mappingsId = mappingsVersion.substring(mappingsVersion.lastIndexOf('_') + 1).toInt() 58 | var minecraftVersion: String? = null 59 | for ((version, byChannel) in mappingsVersions.entries) { 60 | if (mappingsChannel !in byChannel) { 61 | System.err.println("Unknown channel $mappingsChannel for version $version") 62 | exitProcess(1) 63 | } 64 | if (mappingsId in byChannel[mappingsChannel]!!) { 65 | println("Found mappings $mappingsId for version $version") 66 | minecraftVersion = version 67 | break 68 | } 69 | } 70 | if (minecraftVersion == null) { 71 | System.err.println("Unable to find mappings: $mappingsVersion") 72 | exitProcess(1) 73 | } 74 | // Parse the mappings data files 75 | try { 76 | val url = 77 | URL("http://export.mcpbot.bspk.rs/mcp_$fullMappingsChannel/$mappingsId-$minecraftVersion/mcp_$fullMappingsChannel-$mappingsId-$minecraftVersion.zip") 78 | println("Downloading MCP mappings from: $url") 79 | ZipInputStream(url.openStream()).use { 80 | var entry = it.nextEntry 81 | do { 82 | when (entry.name) { 83 | "fields.csv" -> { 84 | CSVReader(it.reader()).forEachLine { 85 | val original = it[0] 86 | val renamed = it[1] 87 | fieldNames[original] = renamed 88 | } 89 | } 90 | "methods.csv" -> { 91 | CSVReader(it.reader()).forEachLine { 92 | val original = it[0] 93 | val renamed = it[1] 94 | methodNames[original] = renamed 95 | } 96 | } 97 | } 98 | entry = it.nextEntry 99 | } while (entry != null) 100 | } 101 | if (fieldNames.isEmpty() || methodNames.isEmpty()) { 102 | System.err.println("Unable to download MCP mappings $mappingsVersion: Unable to locate info in the zip file") 103 | exitProcess(1) 104 | } 105 | } catch (e: IOException) { 106 | System.err.println("Unable to download MCP mappings $mappingsVersion:") 107 | e.printStackTrace() 108 | exitProcess(1) 109 | } 110 | val json = JsonObject() 111 | json["fields"] = Gson().toJsonTree(fieldNames) 112 | json["methods"] = Gson().toJsonTree(methodNames) 113 | cacheFile.writeText(json.toString()) 114 | } else { 115 | println("Reading cache $cacheFile") 116 | JsonReader(cacheFile.reader()).use { 117 | it.beginObject() 118 | while (it.hasNext()) { 119 | val name = it.nextName() 120 | if (name == "fields" || name == "methods") { 121 | it.beginObject() 122 | while (it.hasNext()) { 123 | val original = it.nextName() 124 | val renamed = it.nextString() 125 | when (name) { 126 | "fields" -> fieldNames[original] = renamed 127 | "methods" -> methodNames[original] = renamed 128 | } 129 | } 130 | it.endObject() 131 | } 132 | } 133 | it.endObject() 134 | } 135 | } 136 | return Mappings.createRenamingMappings( 137 | { oldType: JavaType -> oldType }, 138 | { srgMethod: MethodData -> methodNames[srgMethod.name] ?: srgMethod.name }, 139 | { srgField: FieldData -> fieldNames[srgField.name] ?: srgField.name } 140 | ).transform(srgMappings.inverted()) 141 | } 142 | 143 | fun downloadSrgMappings(minecraftVersion: String): Mappings { 144 | val cacheFile = File("cache/mcp-$minecraftVersion-joined.srg") 145 | if (!cacheFile.exists()) { 146 | cacheFile.parentFile.mkdirs() 147 | try { 148 | val url = 149 | URL("http://maven.minecraftforge.net/de/oceanlabs/mcp/mcp/$minecraftVersion/mcp-$minecraftVersion-srg.zip") 150 | ZipInputStream(url.openStream()).use { zipStream -> 151 | var entry = zipStream.nextEntry 152 | while (entry != null) { 153 | if (entry.name == "joined.srg") { 154 | check(cacheFile.createNewFile()) 155 | cacheFile.outputStream().use { output -> zipStream.copyTo(output) } 156 | } 157 | entry = zipStream.nextEntry 158 | } 159 | } 160 | if (!cacheFile.exists()) { 161 | System.err.println("Unable to download SRG mappings for $minecraftVersion: Unable to locate joined.srg in the zip file") 162 | exitProcess(1) 163 | } 164 | } catch (e: IOException) { 165 | System.err.println("Unable to download SRG mappings for $minecraftVersion:") 166 | e.printStackTrace() 167 | exitProcess(1) 168 | } 169 | } 170 | return MappingsFormat.SEARGE_FORMAT.parseFile(cacheFile) 171 | } 172 | 173 | // use MinecraftForge/MCPConfig to mappings > 1.13 174 | 175 | fun getMCPConfigMappings(minecraftVersion: String): Mappings { 176 | val cacheFolder = File("cache/") 177 | if (!cacheFolder.exists()) { 178 | cacheFolder.mkdirs() 179 | } 180 | val obf2srgFile = File(cacheFolder, "mcpconfig-$minecraftVersion-joined.srg") 181 | 182 | if (!obf2srgFile.exists()) { 183 | println("mcpconfig $minecraftVersion: generating srg from tsrg") 184 | val tsrgFile = File("MCPConfig/versions/release/$minecraftVersion/joined.tsrg") 185 | TSrgUtil.toSrg(tsrgFile, obf2srgFile) 186 | } 187 | 188 | return MappingsFormat.SEARGE_FORMAT.parseFile(obf2srgFile) 189 | } 190 | -------------------------------------------------------------------------------- /src/main/kotlin/provider/mojang.kt: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import com.google.gson.JsonParser 4 | import net.techcable.srglib.format.MappingsFormat 5 | import net.techcable.srglib.mappings.Mappings 6 | import org.cadixdev.lorenz.MappingSet 7 | import org.cadixdev.lorenz.io.proguard.ProGuardFormat 8 | import org.cadixdev.lorenz.io.srg.SrgWriter 9 | import java.io.* 10 | import java.net.URL 11 | import java.nio.charset.StandardCharsets 12 | 13 | /** 14 | * Reads the client mappings from the URLs stored in the client json. 15 | * 16 | * Written in Java and translated to Kotlin 17 | * 18 | * @author phase 19 | */ 20 | object MojangMappings { 21 | 22 | fun getMappings(version: String): Mappings { 23 | val cacheDir = File("cache/mojang/") 24 | cacheDir.mkdirs() 25 | download(version, cacheDir) 26 | val clientMappingFile = File(cacheDir, version + "_client.srg") 27 | return MappingsFormat.SEARGE_FORMAT.parseLines(clientMappingFile.readLines().filter { 28 | !it.contains("package-info") 29 | && !".*(\\$\\d+).*".toRegex().matches(it) 30 | }) 31 | } 32 | 33 | fun download(version: String, dir: File?) { 34 | val clientMappingFile = File(dir, version + "_client.srg") 35 | val serverMappingFile = File(dir, version + "_server.srg") 36 | 37 | if (clientMappingFile.exists() && serverMappingFile.exists()) return; 38 | 39 | val mappings = readMappings(version) 40 | 41 | val clientWriter = PrintWriter(FileWriter(clientMappingFile)) 42 | SrgWriter(clientWriter).write(mappings.clientMappings) 43 | clientWriter.close() 44 | 45 | val serverWriter = PrintWriter(FileWriter(serverMappingFile)) 46 | SrgWriter(serverWriter).write(mappings.serverMappings) 47 | serverWriter.close() 48 | } 49 | 50 | fun readMappings(version: String): LorenzMappings { 51 | val versionJsonFile = 52 | File(minecraftFolder, "/versions/$version/$version.json") 53 | val versionJson = 54 | JsonParser().parse(FileReader(versionJsonFile)).asJsonObject 55 | val downloads = versionJson.getAsJsonObject("downloads") 56 | val clientMappingUrl = 57 | URL(downloads.getAsJsonObject("client_mappings")["url"].asString) 58 | val serverMappingUrl = 59 | URL(downloads.getAsJsonObject("server_mappings")["url"].asString) 60 | return LorenzMappings(readUrl(clientMappingUrl), readUrl(serverMappingUrl)) 61 | } 62 | 63 | fun readUrl(url: URL): MappingSet { 64 | println("Downloading from $url") 65 | val conn = url.openConnection() 66 | BufferedReader( 67 | InputStreamReader( 68 | conn.getInputStream(), 69 | StandardCharsets.UTF_8 70 | ) 71 | ).use { reader -> return ProGuardFormat().createReader(reader).read().reverse() } 72 | } 73 | 74 | /** 75 | * @return .minecraft folder for the current OS 76 | */ 77 | val minecraftFolder: File 78 | get() { 79 | val os = System.getProperty("os.name").toLowerCase() 80 | return if (os.contains("win")) { 81 | File(File(System.getenv("APPDATA")), ".minecraft") 82 | } else if (os.contains("mac")) { 83 | File( 84 | File(System.getProperty("user.home")), 85 | "Library/Application Support/minecraft" 86 | ) 87 | } else if (os.contains("linux")) { 88 | File(File(System.getProperty("user.home")), ".minecraft/") 89 | } else { 90 | throw RuntimeException("Failed to determine Minecraft directory for OS: $os") 91 | } 92 | } 93 | 94 | 95 | data class LorenzMappings( 96 | val clientMappings: MappingSet, 97 | val serverMappings: MappingSet 98 | ) 99 | } -------------------------------------------------------------------------------- /src/main/kotlin/provider/spigot.kt: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import com.google.common.collect.ImmutableBiMap 4 | import com.google.common.collect.ImmutableList 5 | import com.google.common.collect.ImmutableMap 6 | import com.google.gson.Gson 7 | import com.google.gson.JsonElement 8 | import com.google.gson.JsonObject 9 | import com.google.gson.JsonParser 10 | import com.google.gson.reflect.TypeToken 11 | import com.google.gson.stream.JsonReader 12 | import com.google.gson.stream.JsonWriter 13 | import com.opencsv.CSVReader 14 | import net.techcable.srglib.FieldData 15 | import net.techcable.srglib.JavaType 16 | import net.techcable.srglib.MethodData 17 | import net.techcable.srglib.format.MappingsFormat 18 | import net.techcable.srglib.mappings.ImmutableMappings 19 | import net.techcable.srglib.mappings.Mappings 20 | import java.io.File 21 | import java.io.Reader 22 | import java.net.URL 23 | import java.util.* 24 | 25 | /** 26 | * Thank you Techcable for being awesome <3 27 | * @author Techcable (MIT License) 28 | */ 29 | 30 | inline fun Gson.fromJson(reader: Reader): T = this.fromJson(reader, (object : TypeToken() {}).type) 31 | 32 | inline fun Gson.fromJson(element: JsonElement): T = 33 | this.fromJson(element, (object : TypeToken() {}).type) 34 | 35 | inline fun CSVReader.forEachLine(action: (Array) -> Unit) { 36 | var line = this.readNext() 37 | while (line != null) { 38 | action(line) 39 | line = this.readNext() 40 | } 41 | } 42 | 43 | fun URL.loadJson(): JsonElement = this.openStream().reader().use { JsonParser().parse(it) } 44 | fun URL.downloadTo(target: File) { 45 | check(target.createNewFile()) { "Unable to download ${this} to $target: File already exists" } 46 | this.openStream().use { input -> 47 | target.outputStream().use { output -> 48 | input.copyTo(output) 49 | } 50 | } 51 | } 52 | 53 | @Suppress("NOTHING_TO_INLINE") 54 | inline operator fun JsonObject.set(key: String, value: JsonElement) = this.add(key, value) 55 | 56 | fun stripDuplicates(mappings: Mappings): ImmutableMappings { 57 | val classes = HashMap() 58 | val fields = HashMap() 59 | val methods = HashMap() 60 | mappings.forEachClass { original, renamed -> 61 | if (original != renamed) { 62 | classes[original] = renamed 63 | } 64 | } 65 | mappings.forEachField { original, renamed -> 66 | if (original.name != renamed.name) { 67 | fields[original] = renamed 68 | } 69 | } 70 | mappings.forEachMethod { original, renamed -> 71 | if (original.name != renamed.name) { 72 | methods[original] = renamed 73 | } 74 | } 75 | return ImmutableMappings.create( 76 | ImmutableBiMap.copyOf(classes), 77 | ImmutableBiMap.copyOf(methods), 78 | ImmutableBiMap.copyOf(fields) 79 | ) 80 | } 81 | 82 | class CacheInfo(val buildDataCommits: MutableMap = HashMap()) { 83 | fun saveTo(file: File) = file.writer().use { Gson().toJson(this, it) } 84 | 85 | companion object { 86 | fun loadFrom(file: File) = file.reader().use { Gson().fromJson(it) } 87 | } 88 | } 89 | 90 | /** 91 | * Get the build data commit from the specified spigot revision 92 | * 93 | * The Spigot revision is specified using '--rev=1.8' as a buildtools option 94 | * Available revisions: https://hub.spigotmc.org/versions/ 95 | * @param spigotVersion the build data revision 96 | * @return the build data commit-id for this revision 97 | */ 98 | fun getBuildDataCommit(spigotVersion: String): String { 99 | val cacheFile = File("cache/info.json") 100 | val cacheInfo = if (cacheFile.exists()) CacheInfo.loadFrom(cacheFile) else CacheInfo() 101 | return cacheInfo.buildDataCommits.getOrElse(spigotVersion) { 102 | val url = URL("https://hub.spigotmc.org/versions/$spigotVersion.json") 103 | val json = url.loadJson().asJsonObject 104 | val buildDataCommit = json.getAsJsonObject("refs").get("BuildData").asString 105 | // Store it in the cache 106 | cacheInfo.buildDataCommits[spigotVersion] = buildDataCommit 107 | cacheInfo.saveTo(cacheFile) 108 | buildDataCommit 109 | } 110 | } 111 | 112 | /** 113 | * Syntax errors in the srg files that SpecialSource swallows silently 114 | */ 115 | val brokenLines = setOf( 116 | "IDispenseBehavior a(LISourceBlock;LItemStack;)LItemStack; dispense", 117 | "nv ServerStatisticManager#", 118 | "ql ServerStatisticManager#", 119 | "qn ServerStatisticManager#" 120 | ) 121 | 122 | fun stripBrokenLines(lines: List) = lines.filter { it !in brokenLines && "" !in it } 123 | 124 | fun downloadSpigotMappings(buildDataCommit: String, modern: Boolean): Mappings { 125 | val baseUrl = "https://hub.spigotmc.org/stash/projects/SPIGOT/repos/builddata/browse/" 126 | val cacheDir = File("cache/spigot_$buildDataCommit") 127 | val classMappingsFile = File(cacheDir, "classes.csrg") 128 | val memberMappingsFile = File(cacheDir, "members.csrg") 129 | val packageMappingsFile = File(cacheDir, "packages.json") 130 | if (!classMappingsFile.exists() || !memberMappingsFile.exists() || !packageMappingsFile.exists()) { 131 | cacheDir.mkdirs() 132 | val info = URL("$baseUrl/info.json?at=$buildDataCommit&raw").loadJson().asJsonObject 133 | val classMappingsLocation = info.get("classMappings").asString 134 | val packageMappingsLocation = info.get("packageMappings").asString 135 | if (!classMappingsFile.exists()) { 136 | URL("$baseUrl/mappings/$classMappingsLocation/?at=$buildDataCommit&raw").downloadTo(classMappingsFile) 137 | } 138 | if (!memberMappingsFile.exists()) { 139 | if (!modern) { 140 | val memberMappingsLocation = info.get("memberMappings").asString 141 | URL("$baseUrl/mappings/$memberMappingsLocation/?at=$buildDataCommit&raw").downloadTo(memberMappingsFile) 142 | } 143 | } 144 | if (!packageMappingsFile.exists()) { 145 | val packages = HashMap() 146 | for (line in URL("$baseUrl/mappings/$packageMappingsLocation/?at=$buildDataCommit&raw").openStream().bufferedReader().lineSequence()) { 147 | if (line.trim().startsWith("#") || line.isEmpty()) { 148 | continue 149 | } 150 | val split = line.trim().split(" ") 151 | var original = split[0] 152 | var renamed = split[1] 153 | if (!original.endsWith("/") || !renamed.endsWith("/")) { 154 | throw RuntimeException("Not a package: $line") 155 | } 156 | // Strip trailing '/' 157 | original = original.substring(0, original.length - 1) 158 | renamed = renamed.substring(0, renamed.length - 1) 159 | // Strip leading '.' if present 160 | if (original.startsWith(".")) { 161 | original = original.substring(1) 162 | } 163 | if (renamed.startsWith(".")) { 164 | renamed = renamed.substring(1) 165 | } 166 | original = original.replace('/', '.') 167 | renamed = renamed.replace('/', '.') 168 | packages[original] = renamed 169 | } 170 | JsonWriter(packageMappingsFile.writer()).use { 171 | it.beginObject() 172 | for ((original, renamed) in packages) { 173 | it.name(original) 174 | it.value(renamed) 175 | } 176 | it.endObject() 177 | } 178 | } 179 | } 180 | val classMappings = MappingsFormat.COMPACT_SEARGE_FORMAT.parseLines(stripBrokenLines(classMappingsFile.readLines())) 181 | 182 | if (modern) { 183 | // 1.18+ 184 | return Mappings.chain(ImmutableList.of(classMappings)) 185 | } 186 | 187 | val memberMappings = 188 | MappingsFormat.COMPACT_SEARGE_FORMAT.parseLines(stripBrokenLines(memberMappingsFile.readLines())) 189 | val packages = JsonReader(packageMappingsFile.reader()).use { 190 | val builder = ImmutableMap.builder() 191 | it.beginObject() 192 | while (it.hasNext()) { 193 | builder.put(it.nextName(), it.nextString()) 194 | } 195 | it.endObject() 196 | builder.build() 197 | } 198 | return Mappings.chain(ImmutableList.of(classMappings, memberMappings, Mappings.createPackageMappings(packages))) 199 | } -------------------------------------------------------------------------------- /src/main/kotlin/provider/yarn.kt: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import cuchaz.enigma.ProgressListener 4 | import cuchaz.enigma.translation.mapping.serde.MappingFormat 5 | import net.techcable.srglib.format.MappingsFormat 6 | import net.techcable.srglib.mappings.Mappings 7 | import java.io.File 8 | import java.net.URL 9 | import java.util.concurrent.TimeUnit 10 | import java.util.zip.GZIPInputStream 11 | 12 | fun String.runCommand(workingDir: File) { 13 | ProcessBuilder(*split(" ").toTypedArray()) 14 | .directory(workingDir) 15 | .redirectOutput(ProcessBuilder.Redirect.INHERIT) 16 | .redirectError(ProcessBuilder.Redirect.INHERIT) 17 | .start() 18 | .waitFor(60, TimeUnit.MINUTES) 19 | } 20 | 21 | fun getYarnVersion(minecraftVersion: String): String { 22 | val url = URL("https://meta.fabricmc.net/v1/versions/mappings/$minecraftVersion") 23 | val versionJson = url.loadJson().asJsonArray[0].asJsonObject 24 | return versionJson.get("version").asString 25 | } 26 | 27 | fun downloadYarn(yarnVersion: String, file: File) { 28 | URL("https://maven.fabricmc.net/net/fabricmc/yarn/$yarnVersion/yarn-$yarnVersion-tiny.gz").downloadTo(file) 29 | } 30 | 31 | fun getYarnMappings(minecraftVersion: String): Map { 32 | val yarnMavenVersion = getYarnVersion(minecraftVersion) 33 | val yarnZip = File("cache/yarn-$yarnMavenVersion.gz") 34 | if (!yarnZip.exists()) { 35 | downloadYarn(yarnMavenVersion, yarnZip) 36 | } 37 | val tinyMappings = tiny.Mappings() 38 | GZIPInputStream(yarnZip.inputStream()).use { zip -> 39 | var namespaces = listOf() 40 | zip.reader().readLines().forEach { line -> 41 | val parts = line.split("\t") 42 | when (true) { 43 | line.startsWith("v1") -> { 44 | namespaces = parts.subList(2, parts.size) 45 | tinyMappings.namespaces = namespaces.toMutableList() 46 | } 47 | line.startsWith("CLASS\t") -> { 48 | val clazz = tinyMappings.getClass(parts[1]) 49 | parts.forEachIndexed { index, mapped -> 50 | if (index >= 2) { 51 | val namespace = namespaces[index - 2] 52 | if (namespace != "official") { 53 | clazz.add(namespace, mapped) 54 | } 55 | } 56 | } 57 | } 58 | line.startsWith("FIELD\t") -> { 59 | val field = tinyMappings.getField(parts[1], parts[3], parts[2]) 60 | parts.forEachIndexed { index, mapped -> 61 | if (index >= 4) { 62 | val namespace = namespaces[index - 4] 63 | if (namespace != "official") { 64 | 65 | val broken = arrayOf("field_24240", "field_24245") 66 | 67 | //if (!(mapped in broken)) { 68 | field.add(namespace, mapped) 69 | //} else { 70 | // println("Test: $namespace $mapped") 71 | //} 72 | } 73 | } 74 | } 75 | } 76 | line.startsWith("METHOD\t") -> { 77 | val method = tinyMappings.getMethod(parts[1], parts[3], parts[2]) 78 | parts.forEachIndexed { index, mapped -> 79 | if (index >= 4) { 80 | val namespace = namespaces[index - 4] 81 | if (namespace != "official") { 82 | try { 83 | method.add(namespace, mapped) 84 | } catch (e: Exception) { 85 | e.printStackTrace() 86 | println(line) 87 | } 88 | } 89 | } 90 | } 91 | } 92 | 93 | true -> TODO() 94 | false -> TODO() 95 | } 96 | } 97 | } 98 | tinyMappings.toMappings().forEach { namespace, mappings -> 99 | println("yarn $minecraftVersion $namespace: parsed class=${mappings.classes().size} method=${mappings.fields().size} field=${mappings.methods().size}") 100 | } 101 | return tinyMappings.toMappings() 102 | } 103 | 104 | fun getYarnMappingsFromSubmodule(minecraftVersion: String): Mappings { 105 | val yarnFolder = File("yarn") 106 | val mappingsFile = File("cache/yarn-$minecraftVersion.srg") 107 | if (mappingsFile.exists()) { 108 | println("yarn $minecraftVersion: yarn-$minecraftVersion.srg already exists") 109 | return MappingsFormat.SEARGE_FORMAT.parseFile(mappingsFile) 110 | } 111 | 112 | println("yarn $minecraftVersion: checking out branch $minecraftVersion") 113 | "git checkout $minecraftVersion".runCommand(yarnFolder) 114 | 115 | println("yarn $minecraftVersion: reading mappings directory") 116 | val entryTree = MappingFormat.ENIGMA_DIRECTORY.read(File(yarnFolder, "mappings").toPath(), ProgressListener.VOID) 117 | println("yarn $minecraftVersion: writing mappings to srg") 118 | MappingFormat.SRG_FILE.write(entryTree, mappingsFile.toPath(), ProgressListener.VOID) 119 | 120 | // TODO: fix these 121 | val brokenClasses = listOf( 122 | "", "WoodlandMansionGenerator", 123 | "VoxelSet", "ParticleManager", "PointOfInterestDebugRenderer", "NumberRange", "ServerLightingProvider", 124 | "SpellcastingIllagerEntity", "NetherFortressGenerator", "TextureUtil" 125 | ) 126 | mappingsFile.writeText(mappingsFile.readLines().filter { 127 | brokenClasses.map { b -> !it.contains(b) }.foldRight(true) { a, b -> a && b } 128 | }.joinToString("\n")) 129 | 130 | return MappingsFormat.SEARGE_FORMAT.parseFile(mappingsFile) 131 | } -------------------------------------------------------------------------------- /src/main/kotlin/tiny/tiny.kt: -------------------------------------------------------------------------------- 1 | package tiny 2 | 3 | import com.google.common.collect.ImmutableBiMap 4 | import net.techcable.srglib.FieldData 5 | import net.techcable.srglib.JavaType 6 | import net.techcable.srglib.MethodData 7 | import net.techcable.srglib.MethodSignature 8 | import net.techcable.srglib.mappings.ImmutableMappings 9 | import net.techcable.srglib.mappings.Mappings 10 | 11 | /** 12 | * Merge multiple srglib.Mappings into one tiny.Mappings 13 | */ 14 | 15 | class Mappings( 16 | var namespaces: MutableList, 17 | val classes: MutableList, 18 | val fields: MutableList, 19 | val methods: MutableList 20 | ) { 21 | 22 | constructor() : this(mutableListOf(), mutableListOf(), mutableListOf(), mutableListOf()) 23 | 24 | fun addMappings(namespace: String, mappings: Mappings) { 25 | namespaces.add(namespace) 26 | println("tiny: starting conversion for $namespace") 27 | mappings.forEachClass { obf, mapped -> 28 | getClass(obf.name).add(namespace, mapped.name) 29 | } 30 | mappings.forEachField { obf, mapped -> 31 | getField(obf.declaringType.name, obf.name, "Lunk;").add(namespace, mapped.name) 32 | } 33 | mappings.forEachMethod { obf, mapped -> 34 | getMethod(obf.declaringType.name, obf.name, obf.signature.descriptor).add(namespace, mapped.name) 35 | } 36 | } 37 | 38 | fun getClass(source: String): ClassMapping { 39 | var maybeClass = classes.firstOrNull { it.source == source } 40 | if (maybeClass == null) { 41 | maybeClass = ClassMapping(source, mutableMapOf()) 42 | classes.add(maybeClass) 43 | } 44 | return maybeClass 45 | } 46 | 47 | fun getField(sourceClass: String, source: String, desc: String): FieldMapping { 48 | var maybeField = fields.firstOrNull { it.sourceClass == sourceClass && it.source == source && it.desc == desc } 49 | if (maybeField == null) { 50 | maybeField = FieldMapping(sourceClass, source, desc, mutableMapOf()) 51 | fields.add(maybeField) 52 | } 53 | return maybeField 54 | } 55 | 56 | fun getMethod(sourceClass: String, source: String, desc: String): MethodMapping { 57 | var maybeMethod = 58 | methods.firstOrNull { it.sourceClass == sourceClass && it.source == source && it.desc == desc } 59 | if (maybeMethod == null) { 60 | maybeMethod = MethodMapping(sourceClass, source, desc, mutableMapOf()) 61 | methods.add(maybeMethod) 62 | } 63 | return maybeMethod 64 | } 65 | 66 | fun toStrings(): List { 67 | val entryMappings = mutableListOf("v1\tofficial\t${namespaces.joinToString("\t")}") 68 | entryMappings.addAll(classes.map { it.toString(namespaces) }) 69 | entryMappings.addAll(fields.map { it.toString(namespaces) }) 70 | entryMappings.addAll(methods.map { it.toString(namespaces) }) 71 | return entryMappings 72 | } 73 | 74 | fun toMappings(): Map = namespaces.map { namespace -> 75 | val classMappings = ImmutableBiMap.copyOf(classes.mapNotNull { 76 | try { 77 | Pair( 78 | JavaType.fromDescriptor("L${it.source};"), 79 | JavaType.fromDescriptor("L${(it[namespace] ?: it.source).replace('/', '.')};") 80 | ) 81 | } catch (e: Exception) { 82 | e.printStackTrace() 83 | null 84 | } 85 | }.toMap()) 86 | val fieldDatas = fields.mapNotNull { 87 | try { 88 | it to FieldData.create(JavaType.fromDescriptor("L${it.sourceClass};"), it.source) 89 | } catch (e: Exception) { 90 | e.printStackTrace() 91 | null 92 | } 93 | } 94 | val methodDatas = methods.mapNotNull { 95 | try { 96 | it to MethodData.create( 97 | JavaType.fromDescriptor("L${it.sourceClass};"), 98 | it.source, 99 | MethodSignature.fromDescriptor(it.desc) 100 | ) 101 | } catch (e: Exception) { 102 | // e.printStackTrace() // yarn data has numbers in it for some reason? 103 | null 104 | } 105 | } 106 | val fieldMappings = ImmutableBiMap.copyOf(fieldDatas.map { (field, fieldData) -> 107 | Pair(fieldData, fieldData.mapTypes { classMappings[it] ?: it }.withName(field[namespace] ?: field.source)) 108 | }.toMap()) 109 | val methodMappings = ImmutableBiMap.copyOf(methodDatas.map { (method, methodData) -> 110 | Pair( 111 | methodData, 112 | methodData.mapTypes { classMappings[it] ?: it }.withName(method[namespace] ?: method.source) 113 | ) 114 | }.toMap()) 115 | val namespace = when (namespace) { 116 | "named" -> "yarn" 117 | else -> namespace 118 | } 119 | namespace to ImmutableMappings.create(classMappings, methodMappings, fieldMappings) 120 | }.toMap() 121 | } 122 | 123 | interface EntryMapping { 124 | val mappings: MutableMap 125 | val source: String 126 | 127 | fun add(namespace: String, value: String): EntryMapping { 128 | mappings[namespace] = value 129 | return this 130 | } 131 | 132 | operator fun get(namespace: String): String? = mappings[namespace] 133 | 134 | fun toString(namespaces: List): String { 135 | val line = namespaces.joinToString("\t") { get(it) ?: source } 136 | val kind = when (this) { 137 | is ClassMapping -> "CLASS" 138 | is FieldMapping -> "FIELD" 139 | is MethodMapping -> "METHOD" 140 | else -> "UNKNOWN" 141 | } 142 | return "$kind\t${toString()}\t$line".replace('.', '/') 143 | } 144 | } 145 | 146 | class ClassMapping( 147 | override val source: String, 148 | override val mappings: MutableMap 149 | ) : EntryMapping { 150 | override fun toString(): String = source 151 | } 152 | 153 | class FieldMapping( 154 | val sourceClass: String, 155 | override val source: String, 156 | val desc: String, 157 | override val mappings: MutableMap 158 | ) : EntryMapping { 159 | override fun toString(): String = "$sourceClass\t$desc\t$source" 160 | } 161 | 162 | class MethodMapping( 163 | val sourceClass: String, 164 | override val source: String, 165 | val desc: String, 166 | override val mappings: MutableMap 167 | ) : EntryMapping { 168 | override fun toString(): String = "$sourceClass\t$desc\t$source" 169 | } 170 | -------------------------------------------------------------------------------- /src/main/kotlin/tsrgUtil.kt: -------------------------------------------------------------------------------- 1 | import java.io.File 2 | 3 | /** 4 | * Various utility functions for dealing with TSRG. 5 | * This code is super ugly. 6 | * 7 | * @author phase 8 | */ 9 | object TSrgUtil { 10 | 11 | // these classes are using data based on the TSRG format, not the SRG format 12 | 13 | data class Clazz( 14 | var obf: String, var deobf: String, 15 | val fields: MutableList = mutableListOf(), 16 | val methods: MutableList = mutableListOf() 17 | ) { 18 | override fun toString(): String = "$obf $deobf" 19 | } 20 | 21 | data class Field(var obf: String, var deobf: String) { 22 | override fun toString(): String = "$obf $deobf" 23 | } 24 | 25 | data class Method(var obf: String, var obfSig: String, var deobf: String) { 26 | override fun toString(): String = "$obf $obfSig $deobf" 27 | 28 | fun getDeobfSig(classNames: Map): String { 29 | // find what classes need to be replaced in the obfuscated string 30 | val classesToReplace = mutableListOf() 31 | var buffer = "" 32 | var state = false 33 | obfSig.forEach { 34 | when (it) { 35 | 'L' -> { 36 | if (!state) { 37 | buffer = "" 38 | state = true 39 | } else { 40 | // the obfSig contains the letter L, like "GLX" 41 | buffer += it 42 | } 43 | } 44 | ';' -> { 45 | classesToReplace.add(buffer) 46 | state = false 47 | } 48 | else -> { 49 | if (state) { 50 | buffer += it 51 | } 52 | } 53 | } 54 | } 55 | 56 | // replace the obfuscated classes 57 | var deobfSig = obfSig 58 | classesToReplace.forEach { obfClassName -> 59 | if (classNames.containsKey(obfClassName)) { 60 | deobfSig = deobfSig.replace("L$obfClassName;", "L${classNames[obfClassName]!!};") 61 | } 62 | } 63 | return deobfSig 64 | } 65 | } 66 | 67 | fun parseTSrg(lines: List): List { 68 | val classes = mutableListOf() 69 | var currentClass: Clazz? = null 70 | 71 | // parse the lines 72 | lines.forEachIndexed { index, line -> 73 | if (line.startsWith("#") || line.trim().isEmpty()) { // comment 74 | } else if (line.startsWith("\t") || line.startsWith(" ")) { 75 | if (currentClass == null) throw RuntimeException("Parse error on line $index: no class\n$line") 76 | val l = line.trim() 77 | val parts = l.split(" ") 78 | when (parts.size) { 79 | 2 -> { 80 | // field 81 | val obf = parts[0] 82 | val deobf = parts[1] 83 | currentClass!!.fields.add(Field(obf, deobf)) 84 | } 85 | 3 -> { 86 | // method 87 | val obf = parts[0] 88 | val obfSig = parts[1] 89 | val deobf = parts[2] 90 | currentClass!!.methods.add(Method(obf, obfSig, deobf)) 91 | } 92 | else -> throw RuntimeException("Parse error on line $index: too many parts\n$line") 93 | } 94 | } else if (line.contains(" ")) { 95 | currentClass?.let { classes.add(it) } 96 | val parts = line.split(" ") 97 | when (parts.size) { 98 | 2 -> { 99 | // class 100 | val obf = parts[0] 101 | val deobf = parts[1] 102 | currentClass = Clazz(obf, deobf) 103 | } 104 | else -> throw RuntimeException("Parse error on line $index: class definition has too many parts\n$line") 105 | } 106 | } 107 | } 108 | currentClass?.let { 109 | if (!classes.contains(it)) classes.add(it) 110 | } 111 | return classes 112 | } 113 | 114 | fun toSrg(classes: List, srgFile: File) { 115 | val classNames = classes.map { it.obf to it.deobf }.toMap() 116 | val output = StringBuilder() 117 | // write the classes out in SRG format 118 | classes.forEach { clazz -> 119 | if (clazz.obf != clazz.deobf) { 120 | output.append("CL: ${clazz.obf} ${clazz.deobf}\n") 121 | } 122 | 123 | clazz.fields.forEach { field -> 124 | output.append("FD: ${clazz.obf}/${field.obf} ${clazz.deobf}/${field.deobf}\n") 125 | } 126 | 127 | clazz.methods.forEach { method -> 128 | val deobfSig = method.getDeobfSig(classNames) 129 | output.append( 130 | "MD: ${clazz.obf}/${method.obf} ${method.obfSig} " + 131 | "${clazz.deobf}/${method.deobf} $deobfSig\n" 132 | ) 133 | } 134 | } 135 | srgFile.createNewFile(); 136 | srgFile.writeText(output.toString().split("\n").sorted().filter { it.isNotEmpty() }.joinToString("\n")) 137 | } 138 | 139 | fun toSrg(tsrgFile: File, srgFile: File): List { 140 | // checks 141 | srgFile.getParentFile().mkdirs() 142 | if (!(srgFile.exists())) srgFile.createNewFile() 143 | if (srgFile.exists() && !srgFile.isFile) throw RuntimeException("srg path is not a file: $srgFile") 144 | if (!tsrgFile.exists() || !tsrgFile.isFile) throw RuntimeException("tsrg file not found: $tsrgFile") 145 | 146 | val classes = parseTSrg(tsrgFile.readLines()) 147 | 148 | toSrg(classes, srgFile) 149 | return classes 150 | } 151 | 152 | fun fromSrg(srgFile: File, tsrgFile: File) { 153 | // checks 154 | if (!(tsrgFile.exists())) tsrgFile.createNewFile() 155 | if (tsrgFile.exists() && !tsrgFile.isFile) throw RuntimeException("tsrg path is not a file: $tsrgFile") 156 | if (!srgFile.exists() || !srgFile.isFile) throw RuntimeException("srg file not found: $srgFile") 157 | 158 | val lines = srgFile.readLines() 159 | val classes = mutableListOf() 160 | 161 | lines.forEach { line -> 162 | when (true) { 163 | line.startsWith("CL: ") -> { 164 | val l = line.substring(4, line.length) 165 | val parts = l.split(" ") 166 | val obf = parts[0] 167 | val deobf = parts[1] 168 | if (!classes.map { it.obf }.contains(obf)) { 169 | classes.add(Clazz(obf, deobf)) 170 | } 171 | } 172 | line.startsWith("FD: ") -> { 173 | val l = line.substring(4, line.length) 174 | val parts = l.split(" ") 175 | 176 | // obf part 177 | val p0 = parts[0] 178 | val p0s = p0.lastIndexOf('/') 179 | val obfClass = p0.substring(0, p0s) 180 | val obf = p0.substring(p0s + 1, p0.length) 181 | 182 | // deobf part 183 | val p1 = parts[1] 184 | val p1s = p1.lastIndexOf('/') 185 | val deobfClass = p1.substring(0, p1s) 186 | val deobf = p1.substring(p1s + 1, p1.length) 187 | 188 | val eligibleClasses = classes.filter { it.obf == obfClass && it.deobf == deobfClass } 189 | if (eligibleClasses.isNotEmpty()) { 190 | eligibleClasses.last().fields.add(Field(obf, deobf)) 191 | } else { 192 | // this *shouldn't* happen but just in case the ordering of the mappings is weird we will 193 | // add the class to the map 194 | val newClass = Clazz(obfClass, deobfClass, mutableListOf(Field(obf, deobf))) 195 | classes.add(newClass) 196 | } 197 | } 198 | line.startsWith("MD: ") -> { 199 | val l = line.substring(4, line.length) 200 | val parts = l.split(" ") 201 | 202 | // obf part 203 | val p0 = parts[0] 204 | val p0s = p0.lastIndexOf('/') 205 | val obfClass = p0.substring(0, p0s) 206 | val obf = p0.substring(p0s + 1, p0.length) 207 | 208 | val obfSig = parts[1] 209 | 210 | // deobf part 211 | val p2 = parts[2] 212 | val p2s = p2.lastIndexOf('/') 213 | val deobfClass = p2.substring(0, p2s) 214 | val deobf = p2.substring(p2s + 1, p2.length) 215 | 216 | val eligibleClasses = classes.filter { it.obf == obfClass && it.deobf == deobfClass } 217 | if (eligibleClasses.isNotEmpty()) { 218 | eligibleClasses.last().methods.add(Method(obf, obfSig, deobf)) 219 | } else { 220 | val newClass = 221 | Clazz(obfClass, deobfClass, mutableListOf(), mutableListOf(Method(obf, obfSig, deobf))) 222 | classes.add(newClass) 223 | } 224 | } 225 | 226 | else -> {} 227 | } 228 | } 229 | 230 | val output = StringBuilder() 231 | classes.forEach { clazz -> 232 | output.append("$clazz\n") 233 | clazz.fields.forEach { 234 | output.append("\t$it\n") 235 | } 236 | clazz.methods.forEach { 237 | output.append("\t$it\n") 238 | } 239 | } 240 | tsrgFile.writeText(output.toString()) 241 | } 242 | 243 | } 244 | 245 | object MappingsGenerator { 246 | 247 | private data class ClassMapping(val deobf: String, var clientObf: String? = null, var serverObf: String? = null) { 248 | override fun toString(): String = "$deobf (client: $clientObf) (server: $serverObf)" 249 | } 250 | 251 | private data class FieldMapping( 252 | val deobfField: String, 253 | val deobfClass: String, 254 | var clientObfTotal: String? = null, 255 | var serverObfTotal: String? = null 256 | ) { 257 | override fun toString(): String = "$deobfClass.$deobfField (client: $clientObfTotal) (server: $serverObfTotal)" 258 | } 259 | 260 | private inline fun unquote(s: String): String = s.substring(1, s.length - 1) 261 | 262 | /** 263 | * @param classFile CSV file containing MCP v4.3 mappings 264 | * @return Pair(serverOnlyClassesObf, map from serverObf to clientObf) 265 | */ 266 | fun generateClassMappings(classFile: File): Pair, Map> { 267 | val classNames = classFile.readLines().toMutableList() 268 | classNames.removeAt(0) // remove column definition 269 | val classes = mutableListOf() 270 | 271 | classNames.forEach { 272 | val parts = it.split(",") 273 | val deobf = unquote(parts[0]) 274 | val obf = unquote(parts[1]) 275 | val isClient = unquote(parts[4]).toInt() == 0 276 | 277 | var foundClass = false 278 | classes.forEach { 279 | if (it.deobf == deobf && (it.clientObf == null || it.serverObf == null)) { 280 | foundClass = true 281 | if (isClient) it.clientObf = obf else it.serverObf = obf 282 | } 283 | } 284 | 285 | if (!foundClass) { 286 | if (isClient) { 287 | classes.add(ClassMapping(deobf, obf, null)) 288 | } else { 289 | classes.add(ClassMapping(deobf, null, obf)) 290 | } 291 | } 292 | 293 | } 294 | 295 | return Pair( 296 | classes.filter { it.clientObf == null && it.serverObf != null }.map { it.serverObf!! }, 297 | classes.filter { it.clientObf != null && it.serverObf != null }.map { it.serverObf!! to it.clientObf!! }.toMap() 298 | ) 299 | } 300 | 301 | /** 302 | * @param fieldFile CSV file containing MCP v4.3 mappings 303 | * @return TODO 304 | */ 305 | fun generateFieldMappings(fieldFile: File): Map { 306 | val fieldNames = fieldFile.readLines().toMutableList() 307 | fieldNames.removeAt(0) // remove column definition 308 | val fields = mutableListOf() 309 | 310 | fieldNames.forEach { 311 | val parts = it.split(",") 312 | val deobfField = unquote(parts[1]) 313 | val deobfClass = unquote(parts[5]) 314 | val obfField = unquote(parts[2]) 315 | val obfClass = unquote(parts[6]) 316 | val obf = "$obfClass.$obfField" 317 | val isClient = unquote(parts[8]).toInt() == 0 318 | 319 | var foundField = false 320 | fields.forEach { 321 | if (it.deobfClass == deobfClass && it.deobfField == deobfField) { 322 | foundField = true 323 | if (isClient) it.clientObfTotal = obf else it.serverObfTotal = obf 324 | } 325 | } 326 | 327 | if (!foundField) { 328 | if (isClient) { 329 | fields.add(FieldMapping(deobfField, deobfClass, obf, null)) 330 | } else { 331 | fields.add(FieldMapping(deobfField, deobfClass, null, obf)) 332 | } 333 | } 334 | } 335 | 336 | return fields.filter { 337 | it.clientObfTotal != null && it.serverObfTotal != null 338 | && it.clientObfTotal!!.split(".")[1] != 339 | it.serverObfTotal!!.split(".")[1] 340 | }.map { 341 | it.serverObfTotal!! to it.clientObfTotal!! 342 | }.toMap().toSortedMap() 343 | } 344 | 345 | fun generateMethodMappings(methodFile: File): Map { 346 | val methodNames = methodFile.readLines().toMutableList() 347 | methodNames.removeAt(0) // remove column definition 348 | val methods = mutableListOf() 349 | 350 | methodNames.forEach { 351 | val parts = it.split(",") 352 | val deobfName = unquote(parts[1]) 353 | val deobfClass = unquote(parts[5]) 354 | val obfName = unquote(parts[2]) 355 | val sig = unquote(parts[4]) 356 | val obfClass = unquote(parts[6]) 357 | val obf = "$obfClass.$obfName.$sig" 358 | val isClient = unquote(parts[8]).toInt() == 0 359 | 360 | var foundField = false 361 | methods.forEach { 362 | if (it.deobfClass == deobfClass && it.deobfField == deobfName) { 363 | foundField = true 364 | if (isClient) it.clientObfTotal = obf else it.serverObfTotal = obf 365 | } 366 | } 367 | 368 | if (!foundField) { 369 | if (isClient) { 370 | methods.add(FieldMapping(deobfName, deobfClass, obf, null)) 371 | } else { 372 | methods.add(FieldMapping(deobfName, deobfClass, null, obf)) 373 | } 374 | } 375 | } 376 | 377 | return methods.filter { 378 | it.clientObfTotal != null && it.serverObfTotal != null 379 | && it.clientObfTotal!!.split(".")[1] != 380 | it.serverObfTotal!!.split(".")[1] 381 | }.map { 382 | it.serverObfTotal!! to it.clientObfTotal!! 383 | }.toMap().toSortedMap() 384 | } 385 | 386 | } 387 | -------------------------------------------------------------------------------- /src/main/kotlin/version.kt: -------------------------------------------------------------------------------- 1 | import com.google.common.collect.ImmutableList 2 | import com.google.common.collect.MultimapBuilder 3 | import com.google.gson.Gson 4 | import com.google.gson.JsonArray 5 | import com.google.gson.JsonObject 6 | import net.techcable.srglib.FieldData 7 | import net.techcable.srglib.JavaType 8 | import net.techcable.srglib.MethodData 9 | import net.techcable.srglib.format.MappingsFormat 10 | import net.techcable.srglib.mappings.Mappings 11 | import provider.* 12 | import java.io.File 13 | import SpigotMappingType.* 14 | 15 | enum class MinecraftVersion( 16 | val mcVersion: String, 17 | val mcpVersion: String? = null, 18 | val mcpConfig: Boolean = false, 19 | val spigot: SpigotMappingType = NO_SPIGOT, 20 | val yarn: Boolean = false, 21 | val mojang: Boolean = false, 22 | val legacyIntermediary: Boolean = false 23 | ) { 24 | V1_20_4("1.20.4", null, true, MODERN_SPIGOT, true, true, false), 25 | V1_20_3("1.20.3", null, true, MODERN_SPIGOT, true, true, false), 26 | V1_20_2("1.20.2", null, true, MODERN_SPIGOT, true, true, false), 27 | V1_20_1("1.20.1", null, true, MODERN_SPIGOT, true, true, false), 28 | V1_19_4("1.19.4", null, true, MODERN_SPIGOT, true, true, false), 29 | V1_19_2("1.19.2", null, true, MODERN_SPIGOT, true, true, false), 30 | V1_18_2("1.18.2", null, true, MODERN_SPIGOT, true, true, false), 31 | V1_17_1("1.17.1", null, true, SPIGOT, true, true, false), 32 | V1_16_5("1.16.5", null, true, SPIGOT, true, true, false), 33 | V1_16_2("1.16.2", null, true, SPIGOT, true, true, false), 34 | V1_16_1("1.16.1", null, true, SPIGOT, true, true, false), 35 | V1_15_2("1.15.2", "snapshot_20200515", true, SPIGOT, true, true, false), 36 | V1_15_1("1.15.1", "snapshot_20191217", true, SPIGOT, true, true, false), 37 | V1_15("1.15", "snapshot_nodoc_20191212", true, SPIGOT, true, true, false), 38 | V1_14_4("1.14.4", "snapshot_nodoc_20190729", true, SPIGOT, true, true, false), 39 | V1_14_3("1.14.3", "snapshot_nodoc_20190729", true, SPIGOT, true, false, false), 40 | V1_14_2("1.14.2", "stable_nodoc_53", true, SPIGOT, true, false, false), 41 | V1_14_1("1.14.1", "stable_nodoc_51", true, SPIGOT, true, false, false), 42 | V1_14("1.14", "stable_nodoc_49", true, SPIGOT, true, false, false), 43 | V1_13_2("1.13.2", "stable_nodoc_47", true, SPIGOT, false, false, true), 44 | V1_13_1("1.13.1", "stable_nodoc_45", true, SPIGOT, false, false, true), 45 | V1_13("1.13", "stable_nodoc_43", true, SPIGOT, false, false, true), 46 | V1_12_2("1.12.2", "snapshot_nodoc_20180129", false, SPIGOT, false, false, true), 47 | V1_12("1.12", "snapshot_nodoc_20180814", false, SPIGOT, false, false, true), 48 | V1_11_2("1.11.2", "snapshot_nodoc_20170612", false, SPIGOT, false, false, true), 49 | V1_10_2("1.10.2", "snapshot_nodoc_20160703", false, SPIGOT, false, false, false), 50 | V1_9_4("1.9.4", "snapshot_nodoc_20160604", false, SPIGOT, false, false, false), 51 | V1_9("1.9", "snapshot_nodoc_20160516", false, SPIGOT, false, false, false), 52 | V1_8_9("1.8.9", "snapshot_nodoc_20160301", false, SPIGOT, false, false, true), 53 | V1_8_8("1.8.8", "snapshot_nodoc_20151216", false, SPIGOT, false, false, true), 54 | V1_8("1.8", "snapshot_nodoc_20141130", false, SPIGOT, false, false, true), 55 | V1_7_10("1.7.10", "snapshot_nodoc_20140925", true, SPIGOT, false, false); 56 | ; 57 | 58 | fun generateMappings(): List> { 59 | // Mappings, fromObf 60 | val mappings = mutableListOf>() 61 | 62 | if (mcpVersion != null) { 63 | val obf2srgMappings = if (mcpConfig) { 64 | getMCPConfigMappings(mcVersion) 65 | } else { 66 | downloadSrgMappings(mcVersion) 67 | } 68 | val srg2mcpMappings = downloadMcpMappings(obf2srgMappings, mcpVersion) 69 | val obf2mcp = Mappings.chain(ImmutableList.of(obf2srgMappings, srg2mcpMappings)) 70 | mappings.add(Pair(obf2srgMappings, "srg")) 71 | mappings.add(Pair(obf2mcp, "mcp")) 72 | } 73 | if (spigot == SPIGOT || spigot == MODERN_SPIGOT) { 74 | val buildDataCommit = getBuildDataCommit(mcVersion) 75 | val obf2spigotMappings = downloadSpigotMappings(buildDataCommit, spigot == MODERN_SPIGOT) 76 | mappings.add(Pair(obf2spigotMappings, "spigot")) 77 | } 78 | if (yarn) { 79 | val obf2yarnMappingsSet = getYarnMappings(mcVersion) 80 | obf2yarnMappingsSet.forEach { id, m -> mappings.add(Pair(m, id)) } 81 | } 82 | if (legacyIntermediary) { 83 | val obf2legacyIntermediaryMappingsSet = getLegacyIntermediaryMappings(mcVersion) 84 | obf2legacyIntermediaryMappingsSet.forEach { id, m -> mappings.add(Pair(m, id)) } 85 | } 86 | if (mojang) { 87 | val obf2mojangMappingSet = MojangMappings.getMappings(mcVersion) 88 | mappings.add(Pair(obf2mojangMappingSet, "mojang")) 89 | } 90 | 91 | val completeMappings = mutableListOf>() 92 | for (a in mappings) { 93 | val obf2aMappings = a.first 94 | val a2obfMappings = obf2aMappings.inverted() 95 | 96 | completeMappings.add(Pair("obf2${a.second}", obf2aMappings)) 97 | completeMappings.add(Pair("${a.second}2obf", a2obfMappings)) 98 | for (b in mappings) { 99 | if (a != b) { 100 | try { 101 | // some code 102 | val a2bMappings = Mappings.chain(a2obfMappings, b.first) 103 | completeMappings.add(Pair("${a.second}2${b.second}", a2bMappings)) 104 | } catch (e: IllegalArgumentException) { 105 | // handler 106 | println("Failed: ${a.second}2${b.second}") 107 | } 108 | } 109 | } 110 | } 111 | return completeMappings 112 | } 113 | 114 | fun write(mappingsFolder: File) { 115 | val outputFolder = File(mappingsFolder, mcVersion) 116 | outputFolder.mkdirs() 117 | 118 | fun Mappings.writeTo(fileName: String) { 119 | println("$mcVersion: writing mappings to $fileName.srg") 120 | val strippedMappings = stripDuplicates(this) 121 | val srgLines = MappingsFormat.SEARGE_FORMAT.toLines(strippedMappings) 122 | srgLines.sort() 123 | val file = File(outputFolder, "$fileName.srg") 124 | file.createNewFile() 125 | file.bufferedWriter().use { 126 | for (line in srgLines) { 127 | it.write(line) 128 | it.write("\n") 129 | } 130 | } 131 | 132 | println("$mcVersion: writing mappings to $fileName.csrg") 133 | val csrgLines = MappingsFormat.COMPACT_SEARGE_FORMAT.toLines(strippedMappings) 134 | csrgLines.sort() 135 | File(outputFolder, "$fileName.csrg").bufferedWriter().use { 136 | for (line in csrgLines) { 137 | it.write(line) 138 | it.write("\n") 139 | } 140 | } 141 | 142 | println("$mcVersion: writing mappings to $fileName.tsrg") 143 | TSrgUtil.fromSrg(file, File(outputFolder, "$fileName.tsrg")) 144 | } 145 | 146 | // srg & tsrg 147 | val generatedMappings = generateMappings() 148 | generatedMappings.forEach { pair -> 149 | val fileName = pair.first 150 | val mappings = pair.second 151 | mappings.writeTo(fileName) 152 | } 153 | 154 | // tiny 155 | println("$mcVersion: writing tiny mappings to $mcVersion.tiny") 156 | val tinyMappings = tiny.Mappings() 157 | generatedMappings.filter { it.first.startsWith("obf2") }.forEach { pair -> 158 | val name = pair.first.split("2")[1] 159 | 160 | tinyMappings.addMappings(name, pair.second) 161 | } 162 | File(outputFolder, "$mcVersion.tiny").bufferedWriter().use { 163 | for (line in tinyMappings.toStrings()) { 164 | it.write(line) 165 | it.write("\n") 166 | } 167 | } 168 | 169 | // json 170 | val classMappings = 171 | MultimapBuilder.hashKeys(1000).arrayListValues().build>() 172 | val fieldMappings = 173 | MultimapBuilder.hashKeys(1000).arrayListValues().build>() 174 | val methodMappings = 175 | MultimapBuilder.hashKeys(1000).arrayListValues().build>() 176 | generatedMappings.filter { it.first.startsWith("obf2") }.forEach { pair -> 177 | val name = pair.first.split("2")[1] 178 | val mappings = pair.second 179 | mappings.forEachClass { obf, mapped -> classMappings.put(obf, Pair(name, mapped)) } 180 | mappings.forEachField { obf, mapped -> fieldMappings.put(obf, Pair(name, mapped)) } 181 | mappings.forEachMethod { obf, mapped -> methodMappings.put(obf, Pair(name, mapped)) } 182 | println("$mcVersion: generating json for $name") 183 | } 184 | 185 | fun String.lp(): String = split(".").last() 186 | 187 | val classArray = JsonArray() 188 | val fieldArray = JsonArray() 189 | val methodArray = JsonArray() 190 | for (obf in classMappings.keySet()) { 191 | val mappedObj = JsonObject() 192 | mappedObj.addProperty("obf", obf.name.lp()) 193 | classMappings.get(obf).forEach { 194 | mappedObj.addProperty(it.first, it.second.name.lp()) 195 | } 196 | classArray.add(mappedObj) 197 | } 198 | for (obf in fieldMappings.keySet()) { 199 | val mappedObj = JsonObject() 200 | mappedObj.addProperty("obf", obf.declaringType.name.lp() + "." + obf.name.lp()) 201 | fieldMappings.get(obf).forEach { 202 | mappedObj.addProperty(it.first, it.second.declaringType.name.lp() + "." + it.second.name) 203 | } 204 | fieldArray.add(mappedObj) 205 | } 206 | for (obf in methodMappings.keySet()) { 207 | val mappedObj = JsonObject() 208 | mappedObj.addProperty("obf", obf.declaringType.name.lp() + "." + obf.name.lp()) 209 | methodMappings.get(obf).forEach { 210 | mappedObj.addProperty(it.first, it.second.declaringType.name.lp() + "." + it.second.name) 211 | } 212 | methodArray.add(mappedObj) 213 | } 214 | 215 | val bigJson = JsonObject() 216 | bigJson.addProperty("minecraftVersion", mcVersion) 217 | bigJson.add("classes", classArray) 218 | bigJson.add("fields", fieldArray) 219 | bigJson.add("methods", methodArray) 220 | File(outputFolder, "$mcVersion.json").writeText(Gson().toJson(bigJson)) 221 | } 222 | } --------------------------------------------------------------------------------