diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..8eca64d --- /dev/null +++ b/.editorconfig @@ -0,0 +1,25 @@ +# FabricMC editorconfig v1.0 + +root = true + +[*] +charset = utf-8 +indent_style = tab +indent_size = 4 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.{gradle,java}] +ij_continuation_indent_size = 8 +ij_java_imports_layout = $*,|,java.**,|,javax.**,|,*,|,net.fabricmc.** +ij_java_class_count_to_use_import_on_demand = 999 + +[*.{yml,yaml,json,toml,mcmeta}] +indent_size = 2 + +[*.{yml,yaml,properties}] +indent_style = space + +[*.bat] +end_of_line = crlf diff --git a/.gitignore b/.gitignore index e3ff6b0..2466c66 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,36 @@ -local/ +# FabricMC gitignore v1.0 + +# Gradle .gradle/ -out/ build/ +out/ +classes/ + +# IntelliJ Idea .idea/ +*.iml +*.ipr +*.iws -# eclipse +# Eclipse +.eclipse/ +*.launch + +# VS Code +.vscode/ .settings/ bin/ .classpath .project + +# Fleet +.fleet/ + +# MacOS +*.DS_Store + +# Java +hs_err_*.log +replay_*.log +*.hprof +*.jfr diff --git a/HEADER b/HEADER index d5f99fa..5a30d7f 100644 --- a/HEADER +++ b/HEADER @@ -1,13 +1,16 @@ -Copyright (c) 2016, 2017, 2018, 2019 FabricMC +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/README.md b/README.md index 9aed8b9..c76ff6a 100644 --- a/README.md +++ b/README.md @@ -1 +1,8 @@ # Stitch +Stitch is a set of tools for working with and updating mappings in the `.tiny` format. + +Over the last few years, large parts of its functionality have been made redundant by other tools though, and nowadays, it's mostly only used for [intermediary generation and updating](https://fabricmc.net/wiki/tutorial:updating_yarn). + +Preferable alternatives: +- [Mapping IO](https://github.com/FabricMC/mapping-io) for reading/writing/converting mappings +- [Name Proposal](https://github.com/FabricMC/name-proposal) for proposing names in Enigma diff --git a/build.gradle b/build.gradle index 6beae2b..bda3727 100644 --- a/build.gradle +++ b/build.gradle @@ -1,17 +1,20 @@ plugins { - id "java" - id "java-library" - id "maven-publish" - id "net.minecrell.licenser" version "0.4.1" + id 'java-library' + id 'maven-publish' + id 'com.diffplug.spotless' + id 'com.github.johnrengelman.shadow' + id 'checkstyle' } -sourceCompatibility = 1.8 +checkstyle { + configFile = file('checkstyle.xml') +} -def ENV = System.getenv() -version = "0.6.2" + (ENV.GITHUB_ACTIONS ? "" : "+local") +sourceCompatibility = JavaVersion.VERSION_1_8 +targetCompatibility = JavaVersion.VERSION_1_8 -group = 'net.fabricmc' -archivesBaseName = project.name.toLowerCase() +def ENV = System.getenv() +version += (ENV.GITHUB_ACTIONS ? '' : '+local') repositories { mavenCentral() @@ -30,46 +33,37 @@ configurations { } dependencies { - ship 'org.ow2.asm:asm:9.1' - ship 'org.ow2.asm:asm-commons:9.1' - ship 'org.ow2.asm:asm-tree:9.1' - ship 'org.ow2.asm:asm-util:9.1' - ship 'net.fabricmc:tiny-mappings-parser:0.3.0+build.17' - implementation 'com.google.guava:guava:28.0-jre' - compileOnly 'org.jetbrains:annotations:20.1.0' - - enigma 'cuchaz:enigma:0.23.2' - - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.1' - testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.1' + ship "org.ow2.asm:asm:${asm_version}" + ship "org.ow2.asm:asm-commons:${asm_version}" + ship "org.ow2.asm:asm-tree:${asm_version}" + ship "org.ow2.asm:asm-util:${asm_version}" + ship "net.fabricmc:tiny-mappings-parser:${tiny_mappings_parser_version}" + implementation "com.google.guava:guava:${guava_version}-jre" + compileOnly "org.jetbrains:annotations:${jetbrains_annotations_version}" + + enigma "cuchaz:enigma:${enigma_version}" + + testImplementation "org.junit.jupiter:junit-jupiter-api:${junit_jupiter_version}" + testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${junit_jupiter_version}" } -license { - header project.file("HEADER") - include "**/*.java" +spotless { + java { + licenseHeaderFile(rootProject.file('HEADER')) + } } jar { manifest { attributes 'Implementation-Title': 'Stitch', 'Implementation-Version': archiveVersion, - 'Main-Class': "net.fabricmc.stitch.Main" + 'Main-Class': 'net.fabricmc.stitch.Main' } } -task allJar(type: Jar) { - from { - configurations.ship.collect { - it.isDirectory() ? it : zipTree(it) - } - } - manifest { - attributes 'Implementation-Title': 'Stitch', - 'Implementation-Version': archiveVersion, - 'Main-Class': "net.fabricmc.stitch.Main" - } +shadowJar { + configurations = [project.configurations.ship] archiveClassifier = 'all' - with jar } java { @@ -77,7 +71,7 @@ java { } tasks.withType(JavaCompile).configureEach { - it.options.encoding = "UTF-8" + it.options.encoding = 'UTF-8' if (JavaVersion.current().isJava9Compatible()) { it.options.release = 8 @@ -88,7 +82,6 @@ publishing { publications { mavenJava(MavenPublication) { from components.java - artifact(allJar) } } @@ -96,7 +89,7 @@ publishing { repositories { if (ENV.MAVEN_URL) { repositories.maven { - name "fabric" + name 'fabric' url ENV.MAVEN_URL credentials { username ENV.MAVEN_USERNAME @@ -114,7 +107,7 @@ test { // A task to ensure that the version being released has not already been released. task checkVersion { doFirst { - def xml = new URL("https://maven.fabricmc.net/net/fabricmc/stitch/maven-metadata.xml").text + def xml = new URL('https://maven.fabricmc.net/net/fabricmc/stitch/maven-metadata.xml').text def metadata = new XmlSlurper().parseText(xml) def versions = metadata.versioning.versions.version*.text(); if (versions.contains(version)) { @@ -123,4 +116,4 @@ task checkVersion { } } -publish.mustRunAfter checkVersion \ No newline at end of file +publish.mustRunAfter checkVersion diff --git a/checkstyle.xml b/checkstyle.xml new file mode 100644 index 0000000..1d5aceb --- /dev/null +++ b/checkstyle.xml @@ -0,0 +1,178 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gradle.properties b/gradle.properties index b103c9c..24bd236 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,23 @@ -name = stitch +# Gradle Properties +org.gradle.jvmargs = -Xmx2G +org.gradle.parallel = true +org.gradle.workers.max = 3 + +# Gradle Plugins +spotless_version = 6.13.0 +shadow_version = 7.1.2 + +# Project Properties +version = 0.6.2 +group = net.fabricmc +archivesBaseName = stitch description = Fabric auxillary Tiny tools -url = https://github.com/FabricMC/stitch \ No newline at end of file +url = https://github.com/FabricMC/stitch + +# Project Dependencies +asm_version = 9.5 +tiny_mappings_parser_version = 0.3.0+build.17 +guava_version = 31.1 +jetbrains_annotations_version = 24.0.1 +enigma_version = 1.0.0 +junit_jupiter_version = 5.9.3 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 62d4c05..943f0cb 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 442d913..b8e0595 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-bin.zip -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists +distributionBase = GRADLE_USER_HOME +distributionPath = wrapper/dists +distributionUrl = https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip +networkTimeout = 10000 +zipStoreBase = GRADLE_USER_HOME +zipStorePath = wrapper/dists diff --git a/gradlew b/gradlew index fbd7c51..65dcd68 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,67 +17,101 @@ # ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the @@ -106,80 +140,105 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=`expr $i + 1` + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 5093609..93e3f59 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -54,7 +55,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -64,21 +65,6 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line @@ -86,17 +72,19 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/settings.gradle b/settings.gradle index 6659293..8c6333a 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,12 @@ -rootProject.name = 'stitch' +pluginManagement { + repositories { + gradlePluginPortal() + mavenCentral(); + } + plugins { + id 'com.diffplug.spotless' version "${spotless_version}" + id 'com.github.johnrengelman.shadow' version "${shadow_version}" + } +} +rootProject.name = 'stitch' diff --git a/src/main/java/net/fabricmc/stitch/Command.java b/src/main/java/net/fabricmc/stitch/Command.java index 9fbb549..21b2021 100644 --- a/src/main/java/net/fabricmc/stitch/Command.java +++ b/src/main/java/net/fabricmc/stitch/Command.java @@ -17,13 +17,13 @@ package net.fabricmc.stitch; public abstract class Command { - public final String name; + public final String name; - public Command(String name) { - this.name = name; - } + public Command(String name) { + this.name = name; + } - public abstract String getHelpString(); - public abstract boolean isArgumentCountValid(int count); - public abstract void run(String[] args) throws Exception; + public abstract String getHelpString(); + public abstract boolean isArgumentCountValid(int count); + public abstract void run(String[] args) throws Exception; } diff --git a/src/main/java/net/fabricmc/stitch/Main.java b/src/main/java/net/fabricmc/stitch/Main.java index 04eb5a4..2548cbf 100644 --- a/src/main/java/net/fabricmc/stitch/Main.java +++ b/src/main/java/net/fabricmc/stitch/Main.java @@ -16,63 +16,78 @@ package net.fabricmc.stitch; -import net.fabricmc.stitch.commands.*; -import net.fabricmc.stitch.commands.tinyv2.CommandMergeTinyV2; -import net.fabricmc.stitch.commands.tinyv2.CommandProposeV2FieldNames; -import net.fabricmc.stitch.commands.tinyv2.CommandReorderTinyV2; - import java.util.Locale; import java.util.Map; import java.util.TreeMap; +import net.fabricmc.stitch.commands.CommandAsmTrace; +import net.fabricmc.stitch.commands.CommandGenerateIntermediary; +import net.fabricmc.stitch.commands.CommandGeneratePrefixRemapper; +import net.fabricmc.stitch.commands.CommandMatcherToTiny; +import net.fabricmc.stitch.commands.CommandMergeJar; +import net.fabricmc.stitch.commands.CommandMergeTiny; +import net.fabricmc.stitch.commands.CommandProposeFieldNames; +import net.fabricmc.stitch.commands.CommandReorderTiny; +import net.fabricmc.stitch.commands.CommandRewriteIntermediary; +import net.fabricmc.stitch.commands.CommandUpdateIntermediary; +import net.fabricmc.stitch.commands.CommandValidateRecords; +import net.fabricmc.stitch.commands.tinyv2.CommandMergeTinyV2; +import net.fabricmc.stitch.commands.tinyv2.CommandProposeV2FieldNames; +import net.fabricmc.stitch.commands.tinyv2.CommandReorderTinyV2; + public class Main { - private static final Map COMMAND_MAP = new TreeMap<>(); + private static final Map COMMAND_MAP = new TreeMap<>(); + + public static void addCommand(Command command) { + COMMAND_MAP.put(command.name.toLowerCase(Locale.ROOT), command); + } + + static { + addCommand(new CommandAsmTrace()); + addCommand(new CommandGenerateIntermediary()); + addCommand(new CommandGeneratePrefixRemapper()); + addCommand(new CommandMatcherToTiny()); + addCommand(new CommandMergeJar()); + addCommand(new CommandMergeTiny()); + addCommand(new CommandProposeFieldNames()); + addCommand(new CommandReorderTiny()); + addCommand(new CommandRewriteIntermediary()); + addCommand(new CommandUpdateIntermediary()); + addCommand(new CommandReorderTinyV2()); + addCommand(new CommandMergeTinyV2()); + addCommand(new CommandProposeV2FieldNames()); + addCommand(new CommandValidateRecords()); + } + + public static void main(String[] args) { + if (args.length == 0 + || !COMMAND_MAP.containsKey(args[0].toLowerCase(Locale.ROOT)) + || !COMMAND_MAP.get(args[0].toLowerCase(Locale.ROOT)).isArgumentCountValid(args.length - 1)) { + if (args.length > 0) { + System.out.println("Invalid command: " + args[0]); + } + + System.out.println("Available commands:"); + + for (Command command : COMMAND_MAP.values()) { + System.out.println("\t" + command.name + " " + command.getHelpString()); + } - public static void addCommand(Command command) { - COMMAND_MAP.put(command.name.toLowerCase(Locale.ROOT), command); - } + System.out.println(); + return; + } - static { - addCommand(new CommandAsmTrace()); - addCommand(new CommandGenerateIntermediary()); - addCommand(new CommandGeneratePrefixRemapper()); - addCommand(new CommandMatcherToTiny()); - addCommand(new CommandMergeJar()); - addCommand(new CommandMergeTiny()); - addCommand(new CommandProposeFieldNames()); - addCommand(new CommandReorderTiny()); - addCommand(new CommandRewriteIntermediary()); - addCommand(new CommandUpdateIntermediary()); - addCommand(new CommandReorderTinyV2()); - addCommand(new CommandMergeTinyV2()); - addCommand(new CommandProposeV2FieldNames()); - addCommand(new CommandValidateRecords()); - } + try { + String[] argsCommand = new String[args.length - 1]; - public static void main(String[] args) { - if (args.length == 0 - || !COMMAND_MAP.containsKey(args[0].toLowerCase(Locale.ROOT)) - || !COMMAND_MAP.get(args[0].toLowerCase(Locale.ROOT)).isArgumentCountValid(args.length - 1)) { - if (args.length > 0) { - System.out.println("Invalid command: " + args[0]); - } - System.out.println("Available commands:"); - for (Command command : COMMAND_MAP.values()) { - System.out.println("\t" + command.name + " " + command.getHelpString()); - } - System.out.println(); - return; - } + if (args.length > 1) { + System.arraycopy(args, 1, argsCommand, 0, argsCommand.length); + } - try { - String[] argsCommand = new String[args.length - 1]; - if (args.length > 1) { - System.arraycopy(args, 1, argsCommand, 0, argsCommand.length); - } - COMMAND_MAP.get(args[0].toLowerCase(Locale.ROOT)).run(argsCommand); - } catch (Exception e) { - e.printStackTrace(); - System.exit(1); - } - } + COMMAND_MAP.get(args[0].toLowerCase(Locale.ROOT)).run(argsCommand); + } catch (Exception e) { + e.printStackTrace(); + System.exit(1); + } + } } diff --git a/src/main/java/net/fabricmc/stitch/commands/CommandAsmTrace.java b/src/main/java/net/fabricmc/stitch/commands/CommandAsmTrace.java index ab82848..0ea1160 100644 --- a/src/main/java/net/fabricmc/stitch/commands/CommandAsmTrace.java +++ b/src/main/java/net/fabricmc/stitch/commands/CommandAsmTrace.java @@ -16,34 +16,34 @@ package net.fabricmc.stitch.commands; -import net.fabricmc.stitch.Command; +import java.io.FileInputStream; +import java.io.PrintWriter; + import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.util.Textifier; import org.objectweb.asm.util.TraceClassVisitor; -import java.io.FileInputStream; -import java.io.PrintWriter; +import net.fabricmc.stitch.Command; public class CommandAsmTrace extends Command { - public CommandAsmTrace() { - super("asmTrace"); - } - - @Override - public String getHelpString() { - return ""; - } - - @Override - public boolean isArgumentCountValid(int count) { - return count == 1; - } - - @Override - public void run(String[] args) throws Exception { - ClassReader cr = new ClassReader(new FileInputStream(args[0])); - ClassVisitor tcv = new TraceClassVisitor(new PrintWriter(System.out)); - cr.accept(tcv, 0); - } + public CommandAsmTrace() { + super("asmTrace"); + } + + @Override + public String getHelpString() { + return ""; + } + + @Override + public boolean isArgumentCountValid(int count) { + return count == 1; + } + + @Override + public void run(String[] args) throws Exception { + ClassReader cr = new ClassReader(new FileInputStream(args[0])); + ClassVisitor tcv = new TraceClassVisitor(new PrintWriter(System.out)); + cr.accept(tcv, 0); + } } diff --git a/src/main/java/net/fabricmc/stitch/commands/CommandGenerateIntermediary.java b/src/main/java/net/fabricmc/stitch/commands/CommandGenerateIntermediary.java index e1e6dd4..23528c5 100644 --- a/src/main/java/net/fabricmc/stitch/commands/CommandGenerateIntermediary.java +++ b/src/main/java/net/fabricmc/stitch/commands/CommandGenerateIntermediary.java @@ -16,62 +16,66 @@ package net.fabricmc.stitch.commands; -import net.fabricmc.stitch.Command; -import net.fabricmc.stitch.representation.*; - -import java.io.*; +import java.io.File; +import java.io.IOException; import java.util.Locale; +import net.fabricmc.stitch.Command; +import net.fabricmc.stitch.representation.JarReader; +import net.fabricmc.stitch.representation.JarRootEntry; + public class CommandGenerateIntermediary extends Command { - public CommandGenerateIntermediary() { - super("generateIntermediary"); - } + public CommandGenerateIntermediary() { + super("generateIntermediary"); + } + + @Override + public String getHelpString() { + return " [-t|--target-namespace ] [-p|--obfuscation-pattern ]..."; + } - @Override - public String getHelpString() { - return " [-t|--target-namespace ] [-p|--obfuscation-pattern ]..."; - } + @Override + public boolean isArgumentCountValid(int count) { + return count >= 2; + } - @Override - public boolean isArgumentCountValid(int count) { - return count >= 2; - } + @Override + public void run(String[] args) throws Exception { + File file = new File(args[0]); + JarRootEntry jarEntry = new JarRootEntry(file); - @Override - public void run(String[] args) throws Exception { - File file = new File(args[0]); - JarRootEntry jarEntry = new JarRootEntry(file); - try { - JarReader reader = new JarReader(jarEntry); - reader.apply(); - } catch (IOException e) { - e.printStackTrace(); - } + try { + JarReader reader = new JarReader(jarEntry); + reader.apply(); + } catch (IOException e) { + e.printStackTrace(); + } - GenState state = new GenState(); - boolean clearedPatterns = false; + GenState state = new GenState(); + boolean clearedPatterns = false; - for (int i = 2; i < args.length; i++) { - switch (args[i].toLowerCase(Locale.ROOT)) { - case "-t": - case "--target-namespace": - state.setTargetNamespace(args[i + 1]); - i++; - break; - case "-p": - case "--obfuscation-pattern": - if (!clearedPatterns) - state.clearObfuscatedPatterns(); - clearedPatterns = true; + for (int i = 2; i < args.length; i++) { + switch (args[i].toLowerCase(Locale.ROOT)) { + case "-t": + case "--target-namespace": + state.setTargetNamespace(args[i + 1]); + i++; + break; + case "-p": + case "--obfuscation-pattern": + if (!clearedPatterns) { + state.clearObfuscatedPatterns(); + } - state.addObfuscatedPattern(args[i + 1]); - i++; - break; - } - } + clearedPatterns = true; + state.addObfuscatedPattern(args[i + 1]); + i++; + break; + } + } - System.err.println("Generating new mappings..."); - state.generate(new File(args[1]), jarEntry, null); - System.err.println("Done!"); - } + System.err.println("Generating new mappings..."); + state.generate(new File(args[1]), jarEntry, null); + System.err.println("Done!"); + } } diff --git a/src/main/java/net/fabricmc/stitch/commands/CommandGeneratePrefixRemapper.java b/src/main/java/net/fabricmc/stitch/commands/CommandGeneratePrefixRemapper.java index 60bf59f..91bc449 100644 --- a/src/main/java/net/fabricmc/stitch/commands/CommandGeneratePrefixRemapper.java +++ b/src/main/java/net/fabricmc/stitch/commands/CommandGeneratePrefixRemapper.java @@ -16,12 +16,16 @@ package net.fabricmc.stitch.commands; -import net.fabricmc.stitch.Command; - -import java.io.*; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.OutputStreamWriter; import java.util.jar.JarEntry; import java.util.jar.JarInputStream; +import net.fabricmc.stitch.Command; + public class CommandGeneratePrefixRemapper extends Command { public CommandGeneratePrefixRemapper() { super("genPrefixedTiny"); @@ -40,10 +44,10 @@ public boolean isArgumentCountValid(int count) { @Override public void run(String[] args) throws Exception { try (FileInputStream fis = new FileInputStream(new File(args[0])); - FileOutputStream fos = new FileOutputStream(new File(args[2])); - OutputStreamWriter osw = new OutputStreamWriter(fos); - BufferedWriter writer = new BufferedWriter(osw)) { - writer.write("v1\t" + (args.length >= 5 ? args[3] : "input") + "\t" + (args.length >= 5 ? args[4] : "output") + "\n"); + FileOutputStream fos = new FileOutputStream(new File(args[2])); + OutputStreamWriter osw = new OutputStreamWriter(fos); + BufferedWriter writer = new BufferedWriter(osw)) { + writer.write("v1\t" + (args.length >= 5 ? args[3] : "input") + "\t" + (args.length >= 5 ? args[4] : "output") + "\n"); JarInputStream jis = new JarInputStream(fis); JarEntry entry; diff --git a/src/main/java/net/fabricmc/stitch/commands/CommandMatcherToTiny.java b/src/main/java/net/fabricmc/stitch/commands/CommandMatcherToTiny.java index 43069e0..7296644 100644 --- a/src/main/java/net/fabricmc/stitch/commands/CommandMatcherToTiny.java +++ b/src/main/java/net/fabricmc/stitch/commands/CommandMatcherToTiny.java @@ -16,13 +16,19 @@ package net.fabricmc.stitch.commands; -import net.fabricmc.stitch.Command; -import net.fabricmc.stitch.util.MatcherUtil; - -import java.io.*; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; import java.util.HashMap; import java.util.Map; +import net.fabricmc.stitch.Command; +import net.fabricmc.stitch.util.MatcherUtil; + public class CommandMatcherToTiny extends Command { public CommandMatcherToTiny() { super("matcherToTiny"); @@ -45,26 +51,19 @@ public void run(String[] args) throws Exception { Map methodNames = new HashMap<>(); System.out.println("Loading..."); - try ( - FileInputStream fis = new FileInputStream(new File(args[0])); + try (FileInputStream fis = new FileInputStream(new File(args[0])); InputStreamReader isr = new InputStreamReader(fis); - BufferedReader reader = new BufferedReader(isr) - ) { - + BufferedReader reader = new BufferedReader(isr)) { MatcherUtil.read(reader, false, classNames::put, (src, dst) -> fieldNames.put(src.getOwner() + "\t" + src.getDesc() + "\t" + src.getName(), dst.getName()), - (src, dst) -> methodNames.put(src.getOwner() + "\t" + src.getDesc() + "\t" + src.getName(), dst.getName()) - ); + (src, dst) -> methodNames.put(src.getOwner() + "\t" + src.getDesc() + "\t" + src.getName(), dst.getName())); } System.out.println("Saving..."); - try ( - FileOutputStream fos = new FileOutputStream(new File(args[1])); + try (FileOutputStream fos = new FileOutputStream(new File(args[1])); OutputStreamWriter osw = new OutputStreamWriter(fos); - BufferedWriter writer = new BufferedWriter(osw) - ) { - + BufferedWriter writer = new BufferedWriter(osw)) { writer.write("v1\t" + args[2] + "\t" + args[3] + "\n"); for (String s : classNames.keySet()) { diff --git a/src/main/java/net/fabricmc/stitch/commands/CommandMergeJar.java b/src/main/java/net/fabricmc/stitch/commands/CommandMergeJar.java index 4ea1cd6..9768dcf 100644 --- a/src/main/java/net/fabricmc/stitch/commands/CommandMergeJar.java +++ b/src/main/java/net/fabricmc/stitch/commands/CommandMergeJar.java @@ -16,75 +16,72 @@ package net.fabricmc.stitch.commands; -import net.fabricmc.stitch.Command; -import net.fabricmc.stitch.merge.JarMerger; - import java.io.File; -import java.io.FileInputStream; import java.io.FileNotFoundException; -import java.io.FileOutputStream; import java.io.IOException; import java.util.Locale; -public class CommandMergeJar extends Command { - public CommandMergeJar() { - super("mergeJar"); - } +import net.fabricmc.stitch.Command; +import net.fabricmc.stitch.merge.JarMerger; - @Override - public String getHelpString() { - return " [--removeSnowman] [--syntheticparams]"; - } +public class CommandMergeJar extends Command { + public CommandMergeJar() { + super("mergeJar"); + } - @Override - public boolean isArgumentCountValid(int count) { - return count >= 3; - } + @Override + public String getHelpString() { + return " [--removeSnowman] [--syntheticparams]"; + } - @Override - public void run(String[] args) throws Exception { - File in1f = new File(args[0]); - File in2f = new File(args[1]); - File outf = new File(args[2]); - boolean removeSnowman = false, syntheticParams = false; + @Override + public boolean isArgumentCountValid(int count) { + return count >= 3; + } - for (int i = 3; i < args.length; i++) { - if (args[i].startsWith("--")) { - switch (args[i].substring(2).toLowerCase(Locale.ROOT)) { - case "removesnowman": - removeSnowman = true; - break; - case "syntheticparams": - syntheticParams = true; - break; - } - } - } + @Override + public void run(String[] args) throws Exception { + File in1f = new File(args[0]); + File in2f = new File(args[1]); + File outf = new File(args[2]); + boolean removeSnowman = false, syntheticParams = false; - if (!in1f.exists() || !in1f.isFile()) { - throw new FileNotFoundException("Client JAR could not be found!"); - } + for (int i = 3; i < args.length; i++) { + if (args[i].startsWith("--")) { + switch (args[i].substring(2).toLowerCase(Locale.ROOT)) { + case "removesnowman": + removeSnowman = true; + break; + case "syntheticparams": + syntheticParams = true; + break; + } + } + } - if (!in2f.exists() || !in2f.isFile()) { - throw new FileNotFoundException("Server JAR could not be found!"); - } + if (!in1f.exists() || !in1f.isFile()) { + throw new FileNotFoundException("Client JAR could not be found!"); + } - try (JarMerger merger = new JarMerger(in1f, in2f, outf)) { - if (removeSnowman) { - merger.enableSnowmanRemoval(); - } + if (!in2f.exists() || !in2f.isFile()) { + throw new FileNotFoundException("Server JAR could not be found!"); + } - if (syntheticParams) { - merger.enableSyntheticParamsOffset(); - } + try (JarMerger merger = new JarMerger(in1f, in2f, outf)) { + if (removeSnowman) { + merger.enableSnowmanRemoval(); + } - System.out.println("Merging..."); + if (syntheticParams) { + merger.enableSyntheticParamsOffset(); + } - merger.merge(); + System.out.println("Merging..."); + merger.merge(); - System.out.println("Merge completed!"); - } catch (IOException e) { - e.printStackTrace(); - } - } + System.out.println("Merge completed!"); + } catch (IOException e) { + e.printStackTrace(); + } + } } diff --git a/src/main/java/net/fabricmc/stitch/commands/CommandMergeTiny.java b/src/main/java/net/fabricmc/stitch/commands/CommandMergeTiny.java index 29b1572..decae55 100644 --- a/src/main/java/net/fabricmc/stitch/commands/CommandMergeTiny.java +++ b/src/main/java/net/fabricmc/stitch/commands/CommandMergeTiny.java @@ -16,15 +16,23 @@ package net.fabricmc.stitch.commands; -import net.fabricmc.stitch.Command; - import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.IOException; import java.nio.charset.Charset; import java.nio.file.Files; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +import net.fabricmc.stitch.Command; // TODO: Remap descriptors on fields and methods. public class CommandMergeTiny extends Command { @@ -108,30 +116,36 @@ public static class TinyFile { public TinyFile(File f) throws IOException { try (BufferedReader reader = Files.newBufferedReader(f.toPath(), Charset.forName("UTF-8"))) { String[] header = reader.readLine().trim().split("\t"); + if (header.length < 3 || !header[0].trim().equals("v1")) { throw new RuntimeException("Invalid header!"); } typeCount = header.length - 1; indexList = new String[typeCount]; + for (int i = 0; i < typeCount; i++) { indexList[i] = header[i + 1].trim(); } String line; + while ((line = reader.readLine()) != null) { line = line.trim(); + if (line.length() == 0 || line.charAt(0) == '#') { continue; } String[] parts = line.split("\t"); + for (int i = 0; i < parts.length; i++) { parts[i] = parts[i].trim(); } StringBuilder prefix = new StringBuilder(); prefix.append(parts[0]); + for (int i = 1; i < parts.length - typeCount; i++) { prefix.append('\t'); prefix.append(parts[i]); @@ -143,15 +157,18 @@ public TinyFile(File f) throws IOException { for (int i = 0; i < (type == TinyEntryType.CLASS ? path.length - 1 : path.length); i++) { TinyEntry nextParent = parent.getChild(indexList[0], path[i]); + if (nextParent == null) { nextParent = new TinyEntry(TinyEntryType.CLASS, "CLASS"); nextParent.names.put(indexList[0], path[i]); parent.addChild(nextParent, ""); } + parent = nextParent; } TinyEntry entry; + if (type == TinyEntryType.CLASS && parent.containsChild(indexList[0], path[path.length - 1])) { entry = parent.getChild(indexList[0], path[path.length - 1]); } else { @@ -159,8 +176,10 @@ public TinyFile(File f) throws IOException { } String[] names = new String[typeCount]; + for (int i = 0; i < typeCount; i++) { names[i] = parts[parts.length - typeCount + i]; + if (type == TinyEntryType.CLASS) { // add classes by their final inner class name String[] splitly = names[i].split("\\$"); @@ -171,26 +190,25 @@ public TinyFile(File f) throws IOException { } switch (type) { - case CLASS: - parent.addChild(entry, ""); - break; - case FIELD: - case METHOD: - parent.addChild(entry, parts[2]); - break; + case CLASS: + parent.addChild(entry, ""); + break; + case FIELD: + case METHOD: + parent.addChild(entry, parts[2]); + break; } } } } -/* - public String match(String[] entries, String key) { - if (indexMap.containsKey(key)) { - return entries[indexMap.get(key)]; - } else { - return null; - } - } -*/ + + // public String match(String[] entries, String key) { + // if (indexMap.containsKey(key)) { + // return entries[indexMap.get(key)]; + // } else { + // return null; + // } + // } } private List mappingBlankFillOrder = new ArrayList<>(); @@ -221,6 +239,7 @@ private String fixMatch(TinyEntry a, TinyEntry b, String matchA, String index) { // First, map to the shared index name (sharedIndexName) String officialPath = a.names.get(sharedIndexName); TinyEntry officialEntry = a.getParent(); + while (officialEntry.type == TinyEntryType.CLASS) { officialPath = officialEntry.names.get(sharedIndexName) + "$" + officialPath; officialEntry = officialEntry.getParent(); @@ -305,11 +324,12 @@ private String getEntry(TinyEntry a, TinyEntry b, List totalIndexList) { for (String index : totalIndexList) { entry.append('\t'); - String match = getMatch(a, b, index, index); + if (match == null) { for (String s : mappingBlankFillOrder) { match = getMatch(a, b, s, index); + if (match != null) { break; } @@ -345,6 +365,7 @@ public void write(TinyEntry inputA, TinyEntry inputB, String index, String c, Bu Set subKeys = new TreeSet<>(); if (classA != null) subKeys.addAll(classA.getChildRow(index).keySet()); if (classB != null) subKeys.addAll(classB.getChildRow(index).keySet()); + for (String cc : subKeys) { write(classA, classB, index, cc, writer, totalIndexList, indent + 1); } @@ -364,6 +385,7 @@ public void run(File inputAf, File inputBf, File outputf, String... mappingBlank inputB = new TinyFile(inputBf); System.out.println("Processing..."); + try (BufferedWriter writer = Files.newBufferedWriter(outputf.toPath(), Charset.forName("UTF-8"))) { if (!inputA.indexList[0].equals(inputB.indexList[0])) { throw new RuntimeException("TODO"); @@ -374,20 +396,24 @@ public void run(File inputAf, File inputBf, File outputf, String... mappingBlank // Set matchedIndexes = Sets.intersection(inputA.indexMap.keySet(), inputB.indexMap.keySet()); Set matchedIndexes = Collections.singleton(inputA.indexList[0]); List totalIndexList = new ArrayList<>(Arrays.asList(inputA.indexList)); + for (String s : inputB.indexList) { if (!totalIndexList.contains(s)) { totalIndexList.add(s); } } + int totalIndexCount = totalIndexList.size(); // emit header StringBuilder header = new StringBuilder(); header.append("v1"); + for (String s : totalIndexList) { header.append('\t'); header.append(s); } + writer.write(header.append('\n').toString()); // collect classes @@ -401,6 +427,7 @@ public void run(File inputAf, File inputBf, File outputf, String... mappingBlank write(inputA.root, inputB.root, index, c, writer, totalIndexList, 0); } } + System.out.println("Done!"); } @@ -409,8 +436,8 @@ public void run(String[] args) throws Exception { File inputAf = new File(args[0]); File inputBf = new File(args[1]); File outputf = new File(args[2]); - String[] mbforder = new String[args.length - 3]; + for (int i = 3; i < args.length; i++) { mbforder[i - 3] = args[i]; } diff --git a/src/main/java/net/fabricmc/stitch/commands/CommandProposeFieldNames.java b/src/main/java/net/fabricmc/stitch/commands/CommandProposeFieldNames.java index ca72c02..680735f 100644 --- a/src/main/java/net/fabricmc/stitch/commands/CommandProposeFieldNames.java +++ b/src/main/java/net/fabricmc/stitch/commands/CommandProposeFieldNames.java @@ -16,6 +16,16 @@ package net.fabricmc.stitch.commands; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.util.HashMap; +import java.util.Map; + import net.fabricmc.mappings.EntryTriple; import net.fabricmc.mappings.FieldEntry; import net.fabricmc.mappings.Mappings; @@ -23,105 +33,104 @@ import net.fabricmc.stitch.Command; import net.fabricmc.stitch.util.FieldNameFinder; -import java.io.*; -import java.util.*; - public class CommandProposeFieldNames extends Command { - public CommandProposeFieldNames() { - super("proposeFieldNames"); - } - - @Override - public String getHelpString() { - return " "; - } - - @Override - public boolean isArgumentCountValid(int count) { - return count == 3; - } - - @Override - public void run(String[] args) throws Exception { - Map fieldNamesO = new FieldNameFinder().findNames(new File(args[0])); - - System.err.println("Found " + fieldNamesO.size() + " interesting names."); - - // i didn't fuss too much on this... this needs a rewrite once we get a mapping writer library - Map fieldNames = new HashMap<>(); - - Mappings mappings; - try (FileInputStream fileIn = new FileInputStream(new File(args[1]))) { - mappings = MappingsProvider.readTinyMappings(fileIn, false); - } - - try (FileInputStream fileIn = new FileInputStream(new File(args[1])); - FileOutputStream fileOut = new FileOutputStream(new File(args[2])); - InputStreamReader fileInReader = new InputStreamReader(fileIn); - OutputStreamWriter fileOutWriter = new OutputStreamWriter(fileOut); - BufferedReader reader = new BufferedReader(fileInReader); - BufferedWriter writer = new BufferedWriter(fileOutWriter)) { - - int headerPos = -1; - - String line; - while ((line = reader.readLine()) != null) { - String[] tabSplit = line.split("\t"); - - if (headerPos < 0) { - // first line - if (tabSplit.length < 3) { - throw new RuntimeException("Invalid mapping file!"); - } - - for (int i = 2; i < tabSplit.length; i++) { - if (tabSplit[i].equals("named")) { - headerPos = i; - break; - } - } - - if (headerPos < 0) { - throw new RuntimeException("Could not find 'named' mapping position!"); - } - - if (!tabSplit[1].equals("official")) { - for (FieldEntry e : mappings.getFieldEntries()) { - EntryTriple officialFieldMapping = e.get("official"); - String name = fieldNamesO.get(officialFieldMapping); - if (name != null) { - fieldNames.put(e.get(tabSplit[1]), name); - } - } - } else { - fieldNames = fieldNamesO; - } - - mappings = null; // save memory - } else { - // second+ line - if (tabSplit[0].equals("FIELD")) { - EntryTriple key = new EntryTriple(tabSplit[1], tabSplit[3], tabSplit[2]); - String value = tabSplit[headerPos + 2]; - if (value.startsWith("field_") && fieldNames.containsKey(key)) { - tabSplit[headerPos + 2] = fieldNames.get(key); - - StringBuilder builder = new StringBuilder(tabSplit[0]); - for (int i = 1; i < tabSplit.length; i++) { - builder.append('\t'); - builder.append(tabSplit[i]); - } - line = builder.toString(); - } - } - } - - if (!line.endsWith("\n")) { - line = line + "\n"; - } - - writer.write(line); - } - } - } -} \ No newline at end of file + public CommandProposeFieldNames() { + super("proposeFieldNames"); + } + + @Override + public String getHelpString() { + return " "; + } + + @Override + public boolean isArgumentCountValid(int count) { + return count == 3; + } + + @Override + public void run(String[] args) throws Exception { + Map fieldNamesO = new FieldNameFinder().findNames(new File(args[0])); + + System.err.println("Found " + fieldNamesO.size() + " interesting names."); + + // i didn't fuss too much on this... this needs a rewrite once we get a mapping writer library + Map fieldNames = new HashMap<>(); + Mappings mappings; + + try (FileInputStream fileIn = new FileInputStream(new File(args[1]))) { + mappings = MappingsProvider.readTinyMappings(fileIn, false); + } + + try (FileInputStream fileIn = new FileInputStream(new File(args[1])); + FileOutputStream fileOut = new FileOutputStream(new File(args[2])); + InputStreamReader fileInReader = new InputStreamReader(fileIn); + OutputStreamWriter fileOutWriter = new OutputStreamWriter(fileOut); + BufferedReader reader = new BufferedReader(fileInReader); + BufferedWriter writer = new BufferedWriter(fileOutWriter)) { + int headerPos = -1; + String line; + + while ((line = reader.readLine()) != null) { + String[] tabSplit = line.split("\t"); + + if (headerPos < 0) { + // first line + if (tabSplit.length < 3) { + throw new RuntimeException("Invalid mapping file!"); + } + + for (int i = 2; i < tabSplit.length; i++) { + if (tabSplit[i].equals("named")) { + headerPos = i; + break; + } + } + + if (headerPos < 0) { + throw new RuntimeException("Could not find 'named' mapping position!"); + } + + if (!tabSplit[1].equals("official")) { + for (FieldEntry e : mappings.getFieldEntries()) { + EntryTriple officialFieldMapping = e.get("official"); + String name = fieldNamesO.get(officialFieldMapping); + + if (name != null) { + fieldNames.put(e.get(tabSplit[1]), name); + } + } + } else { + fieldNames = fieldNamesO; + } + + mappings = null; // save memory + } else { + // second+ line + if (tabSplit[0].equals("FIELD")) { + EntryTriple key = new EntryTriple(tabSplit[1], tabSplit[3], tabSplit[2]); + String value = tabSplit[headerPos + 2]; + + if (value.startsWith("field_") && fieldNames.containsKey(key)) { + tabSplit[headerPos + 2] = fieldNames.get(key); + StringBuilder builder = new StringBuilder(tabSplit[0]); + + for (int i = 1; i < tabSplit.length; i++) { + builder.append('\t'); + builder.append(tabSplit[i]); + } + + line = builder.toString(); + } + } + } + + if (!line.endsWith("\n")) { + line = line + "\n"; + } + + writer.write(line); + } + } + } +} diff --git a/src/main/java/net/fabricmc/stitch/commands/CommandReorderTiny.java b/src/main/java/net/fabricmc/stitch/commands/CommandReorderTiny.java index 89bcf05..7c6d28c 100644 --- a/src/main/java/net/fabricmc/stitch/commands/CommandReorderTiny.java +++ b/src/main/java/net/fabricmc/stitch/commands/CommandReorderTiny.java @@ -16,11 +16,6 @@ package net.fabricmc.stitch.commands; -import net.fabricmc.mappings.EntryTriple; -import net.fabricmc.mappings.Mappings; -import net.fabricmc.mappings.MappingsProvider; -import net.fabricmc.stitch.Command; - import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; @@ -28,99 +23,112 @@ import java.io.IOException; import java.io.OutputStreamWriter; import java.util.Comparator; -import java.util.Locale; + +import net.fabricmc.mappings.EntryTriple; +import net.fabricmc.mappings.Mappings; +import net.fabricmc.mappings.MappingsProvider; +import net.fabricmc.stitch.Command; public class CommandReorderTiny extends Command { - public CommandReorderTiny() { - super("reorderTiny"); - } - - @Override - public String getHelpString() { - return " [name order...]"; - } - - @Override - public boolean isArgumentCountValid(int count) { - return count >= 4; - } - - private int compareTriples(EntryTriple a, EntryTriple b) { - int c = a.getOwner().compareTo(b.getOwner()); - if (c == 0) { - c = a.getDesc().compareTo(b.getDesc()); - if (c == 0) { - c = a.getName().compareTo(b.getName()); - } - } - return c; - } - - @Override - public void run(String[] args) throws Exception { - File fileOld = new File(args[0]); - File fileNew = new File(args[1]); - String[] names = new String[args.length - 2]; - System.arraycopy(args, 2, names, 0, names.length); - - System.err.println("Loading mapping file..."); - - Mappings input; - try (FileInputStream stream = new FileInputStream(fileOld)) { - input = MappingsProvider.readTinyMappings(stream, false); - } - - System.err.println("Rewriting mappings..."); - - try (FileOutputStream stream = new FileOutputStream(fileNew); - OutputStreamWriter osw = new OutputStreamWriter(stream); - BufferedWriter writer = new BufferedWriter(osw)) { - - StringBuilder firstLineBuilder = new StringBuilder("v1"); - for (String name : names) { - firstLineBuilder.append('\t').append(name); - } - writer.write(firstLineBuilder.append('\n').toString()); - input.getClassEntries().stream().sorted(Comparator.comparing((a) -> a.get(names[0]))).forEach((entry) -> { - try { - StringBuilder s = new StringBuilder("CLASS"); - for (String name : names) { - s.append('\t').append(entry.get(name)); - } - writer.write(s.append('\n').toString()); - } catch (IOException e) { - throw new RuntimeException(e); - } - }); - input.getFieldEntries().stream().sorted((a, b) -> compareTriples(a.get(names[0]), b.get(names[0]))).forEach((entry) -> { - try { - StringBuilder s = new StringBuilder("FIELD"); - EntryTriple first = entry.get(names[0]); - s.append('\t').append(first.getOwner()).append('\t').append(first.getDesc()); - for (String name : names) { - s.append('\t').append(entry.get(name).getName()); - } - writer.write(s.append('\n').toString()); - } catch (IOException e) { - throw new RuntimeException(e); - } - }); - input.getMethodEntries().stream().sorted((a, b) -> compareTriples(a.get(names[0]), b.get(names[0]))).forEach((entry) -> { - try { - StringBuilder s = new StringBuilder("METHOD"); - EntryTriple first = entry.get(names[0]); - s.append('\t').append(first.getOwner()).append('\t').append(first.getDesc()); - for (String name : names) { - s.append('\t').append(entry.get(name).getName()); - } - writer.write(s.append('\n').toString()); - } catch (IOException e) { - throw new RuntimeException(e); - } - }); - } - - System.err.println("Done!"); - } + public CommandReorderTiny() { + super("reorderTiny"); + } + + @Override + public String getHelpString() { + return " [name order...]"; + } + + @Override + public boolean isArgumentCountValid(int count) { + return count >= 4; + } + + private int compareTriples(EntryTriple a, EntryTriple b) { + int c = a.getOwner().compareTo(b.getOwner()); + + if (c == 0) { + c = a.getDesc().compareTo(b.getDesc()); + + if (c == 0) { + c = a.getName().compareTo(b.getName()); + } + } + + return c; + } + + @Override + public void run(String[] args) throws Exception { + File fileOld = new File(args[0]); + File fileNew = new File(args[1]); + String[] names = new String[args.length - 2]; + System.arraycopy(args, 2, names, 0, names.length); + + System.err.println("Loading mapping file..."); + Mappings input; + + try (FileInputStream stream = new FileInputStream(fileOld)) { + input = MappingsProvider.readTinyMappings(stream, false); + } + + System.err.println("Rewriting mappings..."); + + try (FileOutputStream stream = new FileOutputStream(fileNew); + OutputStreamWriter osw = new OutputStreamWriter(stream); + BufferedWriter writer = new BufferedWriter(osw)) { + StringBuilder firstLineBuilder = new StringBuilder("v1"); + + for (String name : names) { + firstLineBuilder.append('\t').append(name); + } + + writer.write(firstLineBuilder.append('\n').toString()); + input.getClassEntries().stream().sorted(Comparator.comparing((a) -> a.get(names[0]))).forEach((entry) -> { + try { + StringBuilder s = new StringBuilder("CLASS"); + + for (String name : names) { + s.append('\t').append(entry.get(name)); + } + + writer.write(s.append('\n').toString()); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + input.getFieldEntries().stream().sorted((a, b) -> compareTriples(a.get(names[0]), b.get(names[0]))).forEach((entry) -> { + try { + StringBuilder s = new StringBuilder("FIELD"); + EntryTriple first = entry.get(names[0]); + s.append('\t').append(first.getOwner()).append('\t').append(first.getDesc()); + + for (String name : names) { + s.append('\t').append(entry.get(name).getName()); + } + + writer.write(s.append('\n').toString()); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + input.getMethodEntries().stream().sorted((a, b) -> compareTriples(a.get(names[0]), b.get(names[0]))).forEach((entry) -> { + try { + StringBuilder s = new StringBuilder("METHOD"); + EntryTriple first = entry.get(names[0]); + s.append('\t').append(first.getOwner()).append('\t').append(first.getDesc()); + + for (String name : names) { + s.append('\t').append(entry.get(name).getName()); + } + + writer.write(s.append('\n').toString()); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + } + System.err.println("Done!"); + } } diff --git a/src/main/java/net/fabricmc/stitch/commands/CommandRewriteIntermediary.java b/src/main/java/net/fabricmc/stitch/commands/CommandRewriteIntermediary.java index b193733..c53ad09 100644 --- a/src/main/java/net/fabricmc/stitch/commands/CommandRewriteIntermediary.java +++ b/src/main/java/net/fabricmc/stitch/commands/CommandRewriteIntermediary.java @@ -16,61 +16,62 @@ package net.fabricmc.stitch.commands; -import net.fabricmc.stitch.Command; -import net.fabricmc.stitch.representation.JarRootEntry; -import net.fabricmc.stitch.representation.JarReader; - import java.io.File; import java.io.IOException; import java.util.Locale; +import net.fabricmc.stitch.Command; +import net.fabricmc.stitch.representation.JarReader; +import net.fabricmc.stitch.representation.JarRootEntry; + public class CommandRewriteIntermediary extends Command { - public CommandRewriteIntermediary() { - super("rewriteIntermediary"); - } + public CommandRewriteIntermediary() { + super("rewriteIntermediary"); + } + + @Override + public String getHelpString() { + return " [--writeAll]"; + } - @Override - public String getHelpString() { - return " [--writeAll]"; - } + @Override + public boolean isArgumentCountValid(int count) { + return count >= 3; + } - @Override - public boolean isArgumentCountValid(int count) { - return count >= 3; - } + @Override + public void run(String[] args) throws Exception { + File fileOld = new File(args[0]); + JarRootEntry jarOld = new JarRootEntry(fileOld); - @Override - public void run(String[] args) throws Exception { - File fileOld = new File(args[0]); - JarRootEntry jarOld = new JarRootEntry(fileOld); - try { - JarReader reader = new JarReader(jarOld); - reader.apply(); - } catch (IOException e) { - e.printStackTrace(); - } + try { + JarReader reader = new JarReader(jarOld); + reader.apply(); + } catch (IOException e) { + e.printStackTrace(); + } - GenState state = new GenState(); + GenState state = new GenState(); - for (int i = 3; i < args.length; i++) { - switch (args[i].toLowerCase(Locale.ROOT)) { - case "--writeall": - state.setWriteAll(true); - break; - } - } + for (int i = 3; i < args.length; i++) { + switch (args[i].toLowerCase(Locale.ROOT)) { + case "--writeall": + state.setWriteAll(true); + break; + } + } - System.err.println("Loading mapping file..."); - state.prepareRewrite(new File(args[1])); + System.err.println("Loading mapping file..."); + state.prepareRewrite(new File(args[1])); - File outFile = new File(args[2]); - if (outFile.exists()) { - outFile.delete(); - } + File outFile = new File(args[2]); - System.err.println("Rewriting mappings..."); - state.generate(outFile, jarOld, jarOld); - System.err.println("Done!"); - } + if (outFile.exists()) { + outFile.delete(); + } + System.err.println("Rewriting mappings..."); + state.generate(outFile, jarOld, jarOld); + System.err.println("Done!"); + } } diff --git a/src/main/java/net/fabricmc/stitch/commands/CommandUpdateIntermediary.java b/src/main/java/net/fabricmc/stitch/commands/CommandUpdateIntermediary.java index 59a1265..c23ed7f 100644 --- a/src/main/java/net/fabricmc/stitch/commands/CommandUpdateIntermediary.java +++ b/src/main/java/net/fabricmc/stitch/commands/CommandUpdateIntermediary.java @@ -16,76 +16,79 @@ package net.fabricmc.stitch.commands; -import net.fabricmc.stitch.Command; -import net.fabricmc.stitch.representation.*; - import java.io.File; import java.io.IOException; import java.util.Locale; +import net.fabricmc.stitch.Command; +import net.fabricmc.stitch.representation.JarReader; +import net.fabricmc.stitch.representation.JarRootEntry; + public class CommandUpdateIntermediary extends Command { - public CommandUpdateIntermediary() { - super("updateIntermediary"); - } + public CommandUpdateIntermediary() { + super("updateIntermediary"); + } + + @Override + public String getHelpString() { + return " [-t|--target-namespace ] [-p|--obfuscation-pattern ]"; + } - @Override - public String getHelpString() { - return " [-t|--target-namespace ] [-p|--obfuscation-pattern ]"; - } + @Override + public boolean isArgumentCountValid(int count) { + return count >= 5; + } - @Override - public boolean isArgumentCountValid(int count) { - return count >= 5; - } + @Override + public void run(String[] args) throws Exception { + File fileOld = new File(args[0]); + JarRootEntry jarOld = new JarRootEntry(fileOld); - @Override - public void run(String[] args) throws Exception { - File fileOld = new File(args[0]); - JarRootEntry jarOld = new JarRootEntry(fileOld); - try { - JarReader reader = new JarReader(jarOld); - reader.apply(); - } catch (IOException e) { - e.printStackTrace(); - } + try { + JarReader reader = new JarReader(jarOld); + reader.apply(); + } catch (IOException e) { + e.printStackTrace(); + } - File fileNew = new File(args[1]); - JarRootEntry jarNew = new JarRootEntry(fileNew); - try { - JarReader reader = new JarReader(jarNew); - reader.apply(); - } catch (IOException e) { - e.printStackTrace(); - } + File fileNew = new File(args[1]); + JarRootEntry jarNew = new JarRootEntry(fileNew); - GenState state = new GenState(); - boolean clearedPatterns = false; + try { + JarReader reader = new JarReader(jarNew); + reader.apply(); + } catch (IOException e) { + e.printStackTrace(); + } - for (int i = 5; i < args.length; i++) { - switch (args[i].toLowerCase(Locale.ROOT)) { - case "-t": - case "--target-namespace": - state.setTargetNamespace(args[i + 1]); - i++; - break; - case "-p": - case "--obfuscation-pattern": - if (!clearedPatterns) - state.clearObfuscatedPatterns(); - clearedPatterns = true; + GenState state = new GenState(); + boolean clearedPatterns = false; - state.addObfuscatedPattern(args[i + 1]); - i++; - break; - } - } + for (int i = 5; i < args.length; i++) { + switch (args[i].toLowerCase(Locale.ROOT)) { + case "-t": + case "--target-namespace": + state.setTargetNamespace(args[i + 1]); + i++; + break; + case "-p": + case "--obfuscation-pattern": + if (!clearedPatterns) { + state.clearObfuscatedPatterns(); + } - System.err.println("Loading remapping files..."); - state.prepareUpdate(new File(args[2]), new File(args[4])); + clearedPatterns = true; + state.addObfuscatedPattern(args[i + 1]); + i++; + break; + } + } - System.err.println("Generating new mappings..."); - state.generate(new File(args[3]), jarNew, jarOld); - System.err.println("Done!"); - } + System.err.println("Loading remapping files..."); + state.prepareUpdate(new File(args[2]), new File(args[4])); + System.err.println("Generating new mappings..."); + state.generate(new File(args[3]), jarNew, jarOld); + System.err.println("Done!"); + } } diff --git a/src/main/java/net/fabricmc/stitch/commands/CommandValidateRecords.java b/src/main/java/net/fabricmc/stitch/commands/CommandValidateRecords.java index f234ad2..015cf00 100644 --- a/src/main/java/net/fabricmc/stitch/commands/CommandValidateRecords.java +++ b/src/main/java/net/fabricmc/stitch/commands/CommandValidateRecords.java @@ -16,12 +16,12 @@ package net.fabricmc.stitch.commands; -import net.fabricmc.stitch.Command; -import net.fabricmc.stitch.util.RecordValidator; - import java.io.File; import java.io.FileNotFoundException; +import net.fabricmc.stitch.Command; +import net.fabricmc.stitch.util.RecordValidator; + public class CommandValidateRecords extends Command { public CommandValidateRecords() { super("validateRecords"); @@ -52,6 +52,7 @@ public void run(String[] args) throws Exception { for (String error : e.errors) { System.err.println(error); } + throw e; } } diff --git a/src/main/java/net/fabricmc/stitch/commands/GenMap.java b/src/main/java/net/fabricmc/stitch/commands/GenMap.java index eaf2937..94862f8 100644 --- a/src/main/java/net/fabricmc/stitch/commands/GenMap.java +++ b/src/main/java/net/fabricmc/stitch/commands/GenMap.java @@ -15,121 +15,127 @@ */ package net.fabricmc.stitch.commands; -import net.fabricmc.mappings.*; -import org.checkerframework.checker.nullness.qual.Nullable; import java.util.HashMap; import java.util.Map; import java.util.function.Function; +import org.checkerframework.checker.nullness.qual.Nullable; + +import net.fabricmc.mappings.ClassEntry; +import net.fabricmc.mappings.EntryTriple; +import net.fabricmc.mappings.FieldEntry; +import net.fabricmc.mappings.Mappings; +import net.fabricmc.mappings.MethodEntry; + public class GenMap { - private static class Class { - private final String name; - private final Map fieldMaps = new HashMap<>(); - private final Map methodMaps = new HashMap<>(); - - public Class(String name) { - this.name = name; - } - } - - private final Map map = new HashMap<>(); - - public GenMap() { - } - - public void addClass(String from, String to) { - map.put(from, new Class(to)); - } - - public void addField(EntryTriple from, EntryTriple to) { - map.get(from.getOwner()).fieldMaps.put(from, to); - } - - public void addMethod(EntryTriple from, EntryTriple to) { - map.get(from.getOwner()).methodMaps.put(from, to); - } - - public void load(Mappings mappings, String from, String to) { - for (ClassEntry classEntry : mappings.getClassEntries()) { - map.put(classEntry.get(from), new Class(classEntry.get(to))); - } - - for (FieldEntry fieldEntry : mappings.getFieldEntries()) { - map.get(fieldEntry.get(from).getOwner()).fieldMaps.put(fieldEntry.get(from), fieldEntry.get(to)); - } - - for (MethodEntry methodEntry : mappings.getMethodEntries()) { - map.get(methodEntry.get(from).getOwner()).methodMaps.put(methodEntry.get(from), methodEntry.get(to)); - } - } - - @Nullable - public String getClass(String from) { - return map.containsKey(from) ? map.get(from).name : null; - } - - @Nullable - private EntryTriple get(EntryTriple entry, Function> mapGetter) { - if (map.containsKey(entry.getOwner())) { - return mapGetter.apply(map.get(entry.getOwner())).get(entry); - } - - return null; - } - - @Nullable - public EntryTriple getField(String owner, String name, String desc) { - return get(new EntryTriple(owner, name, desc), (c) -> c.fieldMaps); - } - - @Nullable - public EntryTriple getField(EntryTriple entry) { - return get(entry, (c) -> c.fieldMaps); - } - - @Nullable - public EntryTriple getMethod(String owner, String name, String desc) { - return get(new EntryTriple(owner, name, desc), (c) -> c.methodMaps); - } - - @Nullable - public EntryTriple getMethod(EntryTriple entry) { - return get(entry, (c) -> c.methodMaps); - } - - public static class Dummy extends GenMap { - public Dummy() { - } - - @Nullable - @Override - public String getClass(String from) { - return from; - } - - @Nullable - @Override - public EntryTriple getField(String owner, String name, String desc) { - return new EntryTriple(owner, name, desc); - } - - @Nullable - @Override - public EntryTriple getField(EntryTriple entry) { - return entry; - } - - @Nullable - @Override - public EntryTriple getMethod(String owner, String name, String desc) { - return new EntryTriple(owner, name, desc); - } - - @Nullable - @Override - public EntryTriple getMethod(EntryTriple entry) { - return entry; - } - } + private static class Class { + private final String name; + private final Map fieldMaps = new HashMap<>(); + private final Map methodMaps = new HashMap<>(); + + Class(String name) { + this.name = name; + } + } + + private final Map map = new HashMap<>(); + + public GenMap() { + } + + public void addClass(String from, String to) { + map.put(from, new Class(to)); + } + + public void addField(EntryTriple from, EntryTriple to) { + map.get(from.getOwner()).fieldMaps.put(from, to); + } + + public void addMethod(EntryTriple from, EntryTriple to) { + map.get(from.getOwner()).methodMaps.put(from, to); + } + + public void load(Mappings mappings, String from, String to) { + for (ClassEntry classEntry : mappings.getClassEntries()) { + map.put(classEntry.get(from), new Class(classEntry.get(to))); + } + + for (FieldEntry fieldEntry : mappings.getFieldEntries()) { + map.get(fieldEntry.get(from).getOwner()).fieldMaps.put(fieldEntry.get(from), fieldEntry.get(to)); + } + + for (MethodEntry methodEntry : mappings.getMethodEntries()) { + map.get(methodEntry.get(from).getOwner()).methodMaps.put(methodEntry.get(from), methodEntry.get(to)); + } + } + + @Nullable + public String getClass(String from) { + return map.containsKey(from) ? map.get(from).name : null; + } + + @Nullable + private EntryTriple get(EntryTriple entry, Function> mapGetter) { + if (map.containsKey(entry.getOwner())) { + return mapGetter.apply(map.get(entry.getOwner())).get(entry); + } + + return null; + } + + @Nullable + public EntryTriple getField(String owner, String name, String desc) { + return get(new EntryTriple(owner, name, desc), (c) -> c.fieldMaps); + } + + @Nullable + public EntryTriple getField(EntryTriple entry) { + return get(entry, (c) -> c.fieldMaps); + } + + @Nullable + public EntryTriple getMethod(String owner, String name, String desc) { + return get(new EntryTriple(owner, name, desc), (c) -> c.methodMaps); + } + + @Nullable + public EntryTriple getMethod(EntryTriple entry) { + return get(entry, (c) -> c.methodMaps); + } + + public static class Dummy extends GenMap { + public Dummy() { + } + + @Nullable + @Override + public String getClass(String from) { + return from; + } + + @Nullable + @Override + public EntryTriple getField(String owner, String name, String desc) { + return new EntryTriple(owner, name, desc); + } + + @Nullable + @Override + public EntryTriple getField(EntryTriple entry) { + return entry; + } + + @Nullable + @Override + public EntryTriple getMethod(String owner, String name, String desc) { + return new EntryTriple(owner, name, desc); + } + + @Nullable + @Override + public EntryTriple getMethod(EntryTriple entry) { + return entry; + } + } } diff --git a/src/main/java/net/fabricmc/stitch/commands/GenState.java b/src/main/java/net/fabricmc/stitch/commands/GenState.java index beab5d8..e9ac60b 100644 --- a/src/main/java/net/fabricmc/stitch/commands/GenState.java +++ b/src/main/java/net/fabricmc/stitch/commands/GenState.java @@ -16,504 +16,555 @@ package net.fabricmc.stitch.commands; -import net.fabricmc.mappings.EntryTriple; -import net.fabricmc.mappings.MappingsProvider; -import net.fabricmc.stitch.representation.*; -import net.fabricmc.stitch.util.MatcherUtil; -import net.fabricmc.stitch.util.Pair; -import net.fabricmc.stitch.util.StitchUtil; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.objectweb.asm.Opcodes; - -import java.io.*; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Scanner; +import java.util.Set; +import java.util.StringJoiner; +import java.util.TreeSet; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.objectweb.asm.Opcodes; + +import net.fabricmc.mappings.EntryTriple; +import net.fabricmc.mappings.MappingsProvider; +import net.fabricmc.stitch.representation.AbstractJarEntry; +import net.fabricmc.stitch.representation.ClassStorage; +import net.fabricmc.stitch.representation.JarClassEntry; +import net.fabricmc.stitch.representation.JarFieldEntry; +import net.fabricmc.stitch.representation.JarMethodEntry; +import net.fabricmc.stitch.representation.JarRootEntry; +import net.fabricmc.stitch.util.MatcherUtil; +import net.fabricmc.stitch.util.Pair; +import net.fabricmc.stitch.util.StitchUtil; + class GenState { - private final Map counters = new HashMap<>(); - private final Map values = new IdentityHashMap<>(); - private GenMap oldToIntermediary, newToOld; - private GenMap newToIntermediary; - private boolean interactive = true; - private boolean writeAll = false; - private Scanner scanner = new Scanner(System.in); - - private String targetNamespace = "net/minecraft/"; - private final List obfuscatedPatterns = new ArrayList(); - - public GenState() { - this.obfuscatedPatterns.add(Pattern.compile("^[^/]*$")); // Default ofbfuscation. Minecraft classes without a package are obfuscated. - } - - public void setWriteAll(boolean writeAll) { - this.writeAll = writeAll; - } - - public void disableInteractive() { - interactive = false; - } - - public String next(AbstractJarEntry entry, String name) { - return name + "_" + values.computeIfAbsent(entry, (e) -> { - int v = counters.getOrDefault(name, 1); - counters.put(name, v + 1); - return v; - }); - } - - public void setTargetNamespace(final String namespace) { - if (namespace.lastIndexOf("/") != (namespace.length() - 1)) - this.targetNamespace = namespace + "/"; - else - this.targetNamespace = namespace; - } - - public void clearObfuscatedPatterns() { - this.obfuscatedPatterns.clear(); - } - - public void addObfuscatedPattern(String regex) throws PatternSyntaxException { - this.obfuscatedPatterns.add(Pattern.compile(regex)); - } - - public void setCounter(String key, int value) { - counters.put(key, value); - } - - public Map getCounters() { - return Collections.unmodifiableMap(counters); - } - - public void generate(File file, JarRootEntry jarEntry, JarRootEntry jarOld) throws IOException { - if (file.exists()) { - System.err.println("Target file exists - loading..."); - newToIntermediary = new GenMap(); - try (FileInputStream inputStream = new FileInputStream(file)) { - newToIntermediary.load( - MappingsProvider.readTinyMappings(inputStream), - "official", - "intermediary" - ); - } - } - - try (FileWriter fileWriter = new FileWriter(file)) { - try (BufferedWriter writer = new BufferedWriter(fileWriter)) { - writer.write("v1\tofficial\tintermediary\n"); - - for (JarClassEntry c : jarEntry.getClasses()) { - addClass(writer, c, jarOld, jarEntry, this.targetNamespace); - } - - writeCounters(writer); - } - } - } - - public static boolean isMappedClass(ClassStorage storage, JarClassEntry c) { - return !c.isAnonymous(); - } - - public static boolean isMappedField(ClassStorage storage, JarClassEntry c, JarFieldEntry f) { - return isUnmappedFieldName(f.getName()); - } - - public static boolean isUnmappedFieldName(String name) { - return name.length() <= 2 || (name.length() == 3 && name.charAt(2) == '_'); - } - - public static boolean isMappedMethod(ClassStorage storage, JarClassEntry c, JarMethodEntry m) { - return isUnmappedMethodName(m.getName()) && m.isSource(storage, c); - } - - public static boolean isUnmappedMethodName(String name) { - return (name.length() <= 2 || (name.length() == 3 && name.charAt(2) == '_')) && name.charAt(0) != '<'; - } - - @Nullable - private String getFieldName(ClassStorage storage, JarClassEntry c, JarFieldEntry f) { - if (!isMappedField(storage, c, f)) { - return null; - } - - if (newToIntermediary != null) { - EntryTriple findEntry = newToIntermediary.getField(c.getFullyQualifiedName(), f.getName(), f.getDescriptor()); - if (findEntry != null) { - if (findEntry.getName().contains("field_")) { - return findEntry.getName(); - } else { - String newName = next(f, "field"); - System.out.println(findEntry.getName() + " is now " + newName); - return newName; - } - } - } - - if (newToOld != null) { - EntryTriple findEntry = newToOld.getField(c.getFullyQualifiedName(), f.getName(), f.getDescriptor()); - if (findEntry != null) { - findEntry = oldToIntermediary.getField(findEntry); - if (findEntry != null) { - if (findEntry.getName().contains("field_")) { - return findEntry.getName(); - } else { - String newName = next(f, "field"); - System.out.println(findEntry.getName() + " is now " + newName); - return newName; - } - } - } - } - - return next(f, "field"); - } - - private final Map methodNames = new IdentityHashMap<>(); - - private String getPropagation(ClassStorage storage, JarClassEntry classEntry) { - if (classEntry == null) { - return ""; - } - - StringBuilder builder = new StringBuilder(classEntry.getFullyQualifiedName()); - List strings = new ArrayList<>(); - String scs = getPropagation(storage, classEntry.getSuperClass(storage)); - if (!scs.isEmpty()) { - strings.add(scs); - } - - for (JarClassEntry ce : classEntry.getInterfaces(storage)) { - scs = getPropagation(storage, ce); - if (!scs.isEmpty()) { - strings.add(scs); - } - } - - if (!strings.isEmpty()) { - builder.append("<-"); - if (strings.size() == 1) { - builder.append(strings.get(0)); - } else { - builder.append("["); - builder.append(StitchUtil.join(",", strings)); - builder.append("]"); - } - } - - return builder.toString(); - } - - private String getNamesListEntry(ClassStorage storage, JarClassEntry classEntry) { - StringBuilder builder = new StringBuilder(getPropagation(storage, classEntry)); - if (classEntry.isInterface()) { - builder.append("(itf)"); - } - - return builder.toString(); - } - - private Set findNames(ClassStorage storageOld, ClassStorage storageNew, JarClassEntry c, JarMethodEntry m, Map> names) { - Set allEntries = new HashSet<>(); - findNames(storageOld, storageNew, c, m, names, allEntries); - return allEntries; - } - - private void findNames(ClassStorage storageOld, ClassStorage storageNew, JarClassEntry c, JarMethodEntry m, Map> names, Set usedMethods) { - if (!usedMethods.add(m)) { - return; - } - - String suffix = "." + m.getName() + m.getDescriptor(); - - if ((m.getAccess() & Opcodes.ACC_BRIDGE) != 0) { - suffix += "(bridge)"; - } - - List ccList = m.getMatchingEntries(storageNew, c); - - for (JarClassEntry cc : ccList) { - EntryTriple findEntry = null; - if (newToIntermediary != null) { - findEntry = newToIntermediary.getMethod(cc.getFullyQualifiedName(), m.getName(), m.getDescriptor()); - if (findEntry != null) { - names.computeIfAbsent(findEntry.getName(), (s) -> new TreeSet<>()).add(getNamesListEntry(storageNew, cc) + suffix); - } - } - - if (findEntry == null && newToOld != null) { - findEntry = newToOld.getMethod(cc.getFullyQualifiedName(), m.getName(), m.getDescriptor()); - if (findEntry != null) { - EntryTriple newToOldEntry = findEntry; - findEntry = oldToIntermediary.getMethod(newToOldEntry); - if (findEntry != null) { - names.computeIfAbsent(findEntry.getName(), (s) -> new TreeSet<>()).add(getNamesListEntry(storageNew, cc) + suffix); - } else { - // more involved... - JarClassEntry oldBase = storageOld.getClass(newToOldEntry.getOwner(), false); - if (oldBase != null) { - JarMethodEntry oldM = oldBase.getMethod(newToOldEntry.getName() + newToOldEntry.getDesc()); - List cccList = oldM.getMatchingEntries(storageOld, oldBase); - - for (JarClassEntry ccc : cccList) { - findEntry = oldToIntermediary.getMethod(ccc.getFullyQualifiedName(), oldM.getName(), oldM.getDescriptor()); - if (findEntry != null) { - names.computeIfAbsent(findEntry.getName(), (s) -> new TreeSet<>()).add(getNamesListEntry(storageOld, ccc) + suffix); - } - } - } - } - } - } - } - - for (JarClassEntry mc : ccList) { - for (Pair pair : mc.getRelatedMethods(m)) { - findNames(storageOld, storageNew, pair.getLeft(), pair.getLeft().getMethod(pair.getRight()), names, usedMethods); - } - } - } - - @Nullable - private String getMethodName(ClassStorage storageOld, ClassStorage storageNew, JarClassEntry c, JarMethodEntry m) { - if (!isMappedMethod(storageNew, c, m)) { - return null; - } - - if (methodNames.containsKey(m)) { - return methodNames.get(m); - } - - if (newToOld != null || newToIntermediary != null) { - Map> names = new HashMap<>(); - Set allEntries = findNames(storageOld, storageNew, c, m, names); - for (JarMethodEntry mm : allEntries) { - if (methodNames.containsKey(mm)) { - return methodNames.get(mm); - } - } - - if (names.size() > 1) { - System.out.println("Conflict detected - matched same target name!"); - List nameList = new ArrayList<>(names.keySet()); - Collections.sort(nameList); - - for (int i = 0; i < nameList.size(); i++) { - String s = nameList.get(i); - System.out.println((i+1) + ") " + s + " <- " + StitchUtil.join(", ", names.get(s))); - } - - if (!interactive) { - throw new RuntimeException("Conflict detected!"); - } - - while (true) { - String cmd = scanner.nextLine(); - int i; - try { - i = Integer.parseInt(cmd); - } catch (NumberFormatException e) { - e.printStackTrace(); - continue; - } - - if (i >= 1 && i <= nameList.size()) { - for (JarMethodEntry mm : allEntries) { - methodNames.put(mm, nameList.get(i - 1)); - } - System.out.println("OK!"); - return nameList.get(i - 1); - } - } - } else if (names.size() == 1) { - String s = names.keySet().iterator().next(); - for (JarMethodEntry mm : allEntries) { - methodNames.put(mm, s); - } - if (s.contains("method_")) { - return s; - } else { - String newName = next(m, "method"); - System.out.println(s + " is now " + newName); - return newName; - } - } - } - - return next(m, "method"); - } - - private void addClass(BufferedWriter writer, JarClassEntry c, ClassStorage storageOld, ClassStorage storage, String translatedPrefix) throws IOException { - String className = c.getName(); - String cname = ""; - String prefixSaved = translatedPrefix; - - if(!this.obfuscatedPatterns.stream().anyMatch(p -> p.matcher(className).matches())) { - translatedPrefix = c.getFullyQualifiedName(); - } else { - if (!isMappedClass(storage, c)) { - cname = c.getName(); - } else { - cname = null; - - if (newToIntermediary != null) { - String findName = newToIntermediary.getClass(c.getFullyQualifiedName()); - if (findName != null) { - String[] r = findName.split("\\$"); - cname = r[r.length - 1]; - if (r.length == 1) { - translatedPrefix = ""; - } - } - } - - if (cname == null && newToOld != null) { - String findName = newToOld.getClass(c.getFullyQualifiedName()); - if (findName != null) { - findName = oldToIntermediary.getClass(findName); - if (findName != null) { - String[] r = findName.split("\\$"); - cname = r[r.length - 1]; - if (r.length == 1) { - translatedPrefix = ""; - } - - } - } - } - - if (cname != null && !cname.contains("class_")) { - String newName = next(c, "class"); - System.out.println(cname + " is now " + newName); - cname = newName; - translatedPrefix = prefixSaved; - } - - if (cname == null) { - cname = next(c, "class"); - } - } - } - - writer.write("CLASS\t" + c.getFullyQualifiedName() + "\t" + translatedPrefix + cname + "\n"); - - for (JarFieldEntry f : c.getFields()) { - String fName = getFieldName(storage, c, f); - if (fName == null) { - fName = f.getName(); - } - - if (fName != null) { - writer.write("FIELD\t" + c.getFullyQualifiedName() - + "\t" + f.getDescriptor() - + "\t" + f.getName() - + "\t" + fName + "\n"); - } - } - - for (JarMethodEntry m : c.getMethods()) { - String mName = getMethodName(storageOld, storage, c, m); - if (mName == null) { - if (!m.getName().startsWith("<") && m.isSource(storage, c)) { - mName = m.getName(); - } - } - - if (mName != null) { - writer.write("METHOD\t" + c.getFullyQualifiedName() - + "\t" + m.getDescriptor() - + "\t" + m.getName() - + "\t" + mName + "\n"); - } - } - - for (JarClassEntry cc : c.getInnerClasses()) { - addClass(writer, cc, storageOld, storage, translatedPrefix + cname + "$"); - } - } - - public void prepareRewrite(File oldMappings) throws IOException { - oldToIntermediary = new GenMap(); - newToOld = new GenMap.Dummy(); - - // TODO: only read once - readCounters(oldMappings); - - try (FileInputStream inputStream = new FileInputStream(oldMappings)) { - oldToIntermediary.load( - MappingsProvider.readTinyMappings(inputStream), - "official", - "intermediary" - ); - } - } - - public void prepareUpdate(File oldMappings, File matches) throws IOException { - oldToIntermediary = new GenMap(); - newToOld = new GenMap(); - - // TODO: only read once - readCounters(oldMappings); - - try (FileInputStream inputStream = new FileInputStream(oldMappings)) { - oldToIntermediary.load( - MappingsProvider.readTinyMappings(inputStream), - "official", - "intermediary" - ); - } - - try (FileReader fileReader = new FileReader(matches)) { - try (BufferedReader reader = new BufferedReader(fileReader)) { - MatcherUtil.read(reader, true, newToOld::addClass, newToOld::addField, newToOld::addMethod); - } - } - } - - private void readCounters(File counterFile) throws IOException { - Path counterPath = getExternalCounterFile(); - - if (counterPath != null && Files.exists(counterPath)) { - counterFile = counterPath.toFile(); - } - - try (FileReader fileReader = new FileReader(counterFile)) { - try (BufferedReader reader = new BufferedReader(fileReader)) { - String line; - while ((line = reader.readLine()) != null) { - if (line.startsWith("# INTERMEDIARY-COUNTER")) { - String[] parts = line.split(" "); - counters.put(parts[2], Integer.parseInt(parts[3])); - } - } - } - } - } - - private void writeCounters(BufferedWriter writer) throws IOException { - StringJoiner counterLines = new StringJoiner("\n"); - - for (Map.Entry counter : counters.entrySet()) { - counterLines.add("# INTERMEDIARY-COUNTER " + counter.getKey() + " " + counter.getValue()); - } - - writer.write(counterLines.toString()); - Path counterPath = getExternalCounterFile(); - - if (counterPath != null) { - Files.write(counterPath, counterLines.toString().getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); - } - } - - private Path getExternalCounterFile() { - if (System.getProperty("stitch.counter") != null) { - return Paths.get(System.getProperty("stitch.counter")); - } - return null; - } + private final Map counters = new HashMap<>(); + private final Map values = new IdentityHashMap<>(); + private GenMap oldToIntermediary, newToOld; + private GenMap newToIntermediary; + private boolean interactive = true; + private boolean writeAll = false; + private Scanner scanner = new Scanner(System.in); + + private String targetNamespace = "net/minecraft/"; + private final List obfuscatedPatterns = new ArrayList(); + + GenState() { + this.obfuscatedPatterns.add(Pattern.compile("^[^/]*$")); // Default obfuscation. Minecraft classes without a package are obfuscated. + } + + public void setWriteAll(boolean writeAll) { + this.writeAll = writeAll; + } + + public void disableInteractive() { + interactive = false; + } + + public String next(AbstractJarEntry entry, String name) { + return name + "_" + values.computeIfAbsent(entry, (e) -> { + int v = counters.getOrDefault(name, 1); + counters.put(name, v + 1); + return v; + }); + } + + public void setTargetNamespace(final String namespace) { + if (namespace.lastIndexOf("/") != (namespace.length() - 1)) { + this.targetNamespace = namespace + "/"; + } else { + this.targetNamespace = namespace; + } + } + + public void clearObfuscatedPatterns() { + this.obfuscatedPatterns.clear(); + } + + public void addObfuscatedPattern(String regex) throws PatternSyntaxException { + this.obfuscatedPatterns.add(Pattern.compile(regex)); + } + + public void setCounter(String key, int value) { + counters.put(key, value); + } + + public Map getCounters() { + return Collections.unmodifiableMap(counters); + } + + public void generate(File file, JarRootEntry jarEntry, JarRootEntry jarOld) throws IOException { + if (file.exists()) { + System.err.println("Target file exists - loading..."); + newToIntermediary = new GenMap(); + + try (FileInputStream inputStream = new FileInputStream(file)) { + newToIntermediary.load( + MappingsProvider.readTinyMappings(inputStream), + "official", + "intermediary" + ); + } + } + + try (FileWriter fileWriter = new FileWriter(file)) { + try (BufferedWriter writer = new BufferedWriter(fileWriter)) { + writer.write("v1\tofficial\tintermediary\n"); + + for (JarClassEntry c : jarEntry.getClasses()) { + addClass(writer, c, jarOld, jarEntry, this.targetNamespace); + } + + writeCounters(writer); + } + } + } + + public static boolean isMappedClass(ClassStorage storage, JarClassEntry c) { + return !c.isAnonymous(); + } + + public static boolean isMappedField(ClassStorage storage, JarClassEntry c, JarFieldEntry f) { + return isUnmappedFieldName(f.getName()); + } + + public static boolean isUnmappedFieldName(String name) { + return name.length() <= 2 || (name.length() == 3 && name.charAt(2) == '_'); + } + + public static boolean isMappedMethod(ClassStorage storage, JarClassEntry c, JarMethodEntry m) { + return isUnmappedMethodName(m.getName()) && m.isSource(storage, c); + } + + public static boolean isUnmappedMethodName(String name) { + return (name.length() <= 2 || (name.length() == 3 && name.charAt(2) == '_')) + && name.charAt(0) != '<'; + } + + @Nullable + private String getFieldName(ClassStorage storage, JarClassEntry c, JarFieldEntry f) { + if (!isMappedField(storage, c, f)) { + return null; + } + + if (newToIntermediary != null) { + EntryTriple findEntry = newToIntermediary.getField(c.getFullyQualifiedName(), f.getName(), f.getDescriptor()); + + if (findEntry != null) { + if (findEntry.getName().contains("field_")) { + return findEntry.getName(); + } else { + String newName = next(f, "field"); + System.out.println(findEntry.getName() + " is now " + newName); + return newName; + } + } + } + + if (newToOld != null) { + EntryTriple findEntry = newToOld.getField(c.getFullyQualifiedName(), f.getName(), f.getDescriptor()); + + if (findEntry != null) { + findEntry = oldToIntermediary.getField(findEntry); + + if (findEntry != null) { + if (findEntry.getName().contains("field_")) { + return findEntry.getName(); + } else { + String newName = next(f, "field"); + System.out.println(findEntry.getName() + " is now " + newName); + return newName; + } + } + } + } + + return next(f, "field"); + } + + private final Map methodNames = new IdentityHashMap<>(); + + private String getPropagation(ClassStorage storage, JarClassEntry classEntry) { + if (classEntry == null) { + return ""; + } + + StringBuilder builder = new StringBuilder(classEntry.getFullyQualifiedName()); + List strings = new ArrayList<>(); + String scs = getPropagation(storage, classEntry.getSuperClass(storage)); + + if (!scs.isEmpty()) { + strings.add(scs); + } + + for (JarClassEntry ce : classEntry.getInterfaces(storage)) { + scs = getPropagation(storage, ce); + + if (!scs.isEmpty()) { + strings.add(scs); + } + } + + if (!strings.isEmpty()) { + builder.append("<-"); + + if (strings.size() == 1) { + builder.append(strings.get(0)); + } else { + builder.append("["); + builder.append(StitchUtil.join(",", strings)); + builder.append("]"); + } + } + + return builder.toString(); + } + + private String getNamesListEntry(ClassStorage storage, JarClassEntry classEntry) { + StringBuilder builder = new StringBuilder(getPropagation(storage, classEntry)); + + if (classEntry.isInterface()) { + builder.append("(itf)"); + } + + return builder.toString(); + } + + private Set findNames(ClassStorage storageOld, ClassStorage storageNew, JarClassEntry c, JarMethodEntry m, Map> names) { + Set allEntries = new HashSet<>(); + findNames(storageOld, storageNew, c, m, names, allEntries); + return allEntries; + } + + private void findNames(ClassStorage storageOld, ClassStorage storageNew, JarClassEntry c, JarMethodEntry m, Map> names, Set usedMethods) { + if (!usedMethods.add(m)) { + return; + } + + String suffix = "." + m.getName() + m.getDescriptor(); + + if ((m.getAccess() & Opcodes.ACC_BRIDGE) != 0) { + suffix += "(bridge)"; + } + + List ccList = m.getMatchingEntries(storageNew, c); + + for (JarClassEntry cc : ccList) { + EntryTriple findEntry = null; + + if (newToIntermediary != null) { + findEntry = newToIntermediary.getMethod(cc.getFullyQualifiedName(), m.getName(), m.getDescriptor()); + + if (findEntry != null) { + names.computeIfAbsent(findEntry.getName(), (s) -> new TreeSet<>()).add(getNamesListEntry(storageNew, cc) + suffix); + } + } + + if (findEntry == null && newToOld != null) { + findEntry = newToOld.getMethod(cc.getFullyQualifiedName(), m.getName(), m.getDescriptor()); + + if (findEntry != null) { + EntryTriple newToOldEntry = findEntry; + findEntry = oldToIntermediary.getMethod(newToOldEntry); + + if (findEntry != null) { + names.computeIfAbsent(findEntry.getName(), (s) -> new TreeSet<>()).add(getNamesListEntry(storageNew, cc) + suffix); + } else { + // more involved... + JarClassEntry oldBase = storageOld.getClass(newToOldEntry.getOwner(), false); + + if (oldBase != null) { + JarMethodEntry oldM = oldBase.getMethod(newToOldEntry.getName() + newToOldEntry.getDesc()); + List cccList = oldM.getMatchingEntries(storageOld, oldBase); + + for (JarClassEntry ccc : cccList) { + findEntry = oldToIntermediary.getMethod(ccc.getFullyQualifiedName(), oldM.getName(), oldM.getDescriptor()); + + if (findEntry != null) { + names.computeIfAbsent(findEntry.getName(), (s) -> new TreeSet<>()).add(getNamesListEntry(storageOld, ccc) + suffix); + } + } + } + } + } + } + } + + for (JarClassEntry mc : ccList) { + for (Pair pair : mc.getRelatedMethods(m)) { + findNames(storageOld, storageNew, pair.getLeft(), pair.getLeft().getMethod(pair.getRight()), names, usedMethods); + } + } + } + + @Nullable + private String getMethodName(ClassStorage storageOld, ClassStorage storageNew, JarClassEntry c, JarMethodEntry m) { + if (!isMappedMethod(storageNew, c, m)) { + return null; + } + + if (methodNames.containsKey(m)) { + return methodNames.get(m); + } + + if (newToOld != null || newToIntermediary != null) { + Map> names = new HashMap<>(); + Set allEntries = findNames(storageOld, storageNew, c, m, names); + + for (JarMethodEntry mm : allEntries) { + if (methodNames.containsKey(mm)) { + return methodNames.get(mm); + } + } + + if (names.size() > 1) { + System.out.println("Conflict detected - matched same target name!"); + List nameList = new ArrayList<>(names.keySet()); + Collections.sort(nameList); + + for (int i = 0; i < nameList.size(); i++) { + String s = nameList.get(i); + System.out.println((i+1) + ") " + s + " <- " + StitchUtil.join(", ", names.get(s))); + } + + if (!interactive) { + throw new RuntimeException("Conflict detected!"); + } + + while (true) { + String cmd = scanner.nextLine(); + int i; + + try { + i = Integer.parseInt(cmd); + } catch (NumberFormatException e) { + e.printStackTrace(); + continue; + } + + if (i >= 1 && i <= nameList.size()) { + for (JarMethodEntry mm : allEntries) { + methodNames.put(mm, nameList.get(i - 1)); + } + + System.out.println("OK!"); + return nameList.get(i - 1); + } + } + } else if (names.size() == 1) { + String s = names.keySet().iterator().next(); + + for (JarMethodEntry mm : allEntries) { + methodNames.put(mm, s); + } + + if (s.contains("method_")) { + return s; + } else { + String newName = next(m, "method"); + System.out.println(s + " is now " + newName); + return newName; + } + } + } + + return next(m, "method"); + } + + private void addClass(BufferedWriter writer, JarClassEntry c, ClassStorage storageOld, ClassStorage storage, String translatedPrefix) throws IOException { + String className = c.getName(); + String cname = ""; + String prefixSaved = translatedPrefix; + + if (!this.obfuscatedPatterns.stream().anyMatch(p -> p.matcher(className).matches())) { + translatedPrefix = c.getFullyQualifiedName(); + } else { + if (!isMappedClass(storage, c)) { + cname = c.getName(); + } else { + cname = null; + + if (newToIntermediary != null) { + String findName = newToIntermediary.getClass(c.getFullyQualifiedName()); + + if (findName != null) { + String[] r = findName.split("\\$"); + cname = r[r.length - 1]; + + if (r.length == 1) { + translatedPrefix = ""; + } + } + } + + if (cname == null && newToOld != null) { + String findName = newToOld.getClass(c.getFullyQualifiedName()); + + if (findName != null) { + findName = oldToIntermediary.getClass(findName); + + if (findName != null) { + String[] r = findName.split("\\$"); + cname = r[r.length - 1]; + + if (r.length == 1) { + translatedPrefix = ""; + } + } + } + } + + if (cname != null && !cname.contains("class_")) { + String newName = next(c, "class"); + System.out.println(cname + " is now " + newName); + cname = newName; + translatedPrefix = prefixSaved; + } + + if (cname == null) { + cname = next(c, "class"); + } + } + } + + writer.write("CLASS\t" + c.getFullyQualifiedName() + "\t" + translatedPrefix + cname + "\n"); + + for (JarFieldEntry f : c.getFields()) { + String fName = getFieldName(storage, c, f); + + if (fName == null) { + fName = f.getName(); + } + + if (fName != null) { + writer.write("FIELD\t" + c.getFullyQualifiedName() + + "\t" + f.getDescriptor() + + "\t" + f.getName() + + "\t" + fName + "\n"); + } + } + + for (JarMethodEntry m : c.getMethods()) { + String mName = getMethodName(storageOld, storage, c, m); + + if (mName == null) { + if (!m.getName().startsWith("<") && m.isSource(storage, c)) { + mName = m.getName(); + } + } + + if (mName != null) { + writer.write("METHOD\t" + c.getFullyQualifiedName() + + "\t" + m.getDescriptor() + + "\t" + m.getName() + + "\t" + mName + "\n"); + } + } + + for (JarClassEntry cc : c.getInnerClasses()) { + addClass(writer, cc, storageOld, storage, translatedPrefix + cname + "$"); + } + } + + public void prepareRewrite(File oldMappings) throws IOException { + oldToIntermediary = new GenMap(); + newToOld = new GenMap.Dummy(); + + // TODO: only read once + readCounters(oldMappings); + + try (FileInputStream inputStream = new FileInputStream(oldMappings)) { + oldToIntermediary.load( + MappingsProvider.readTinyMappings(inputStream), + "official", + "intermediary" + ); + } + } + + public void prepareUpdate(File oldMappings, File matches) throws IOException { + oldToIntermediary = new GenMap(); + newToOld = new GenMap(); + + // TODO: only read once + readCounters(oldMappings); + + try (FileInputStream inputStream = new FileInputStream(oldMappings)) { + oldToIntermediary.load( + MappingsProvider.readTinyMappings(inputStream), + "official", + "intermediary" + ); + } + + try (FileReader fileReader = new FileReader(matches)) { + try (BufferedReader reader = new BufferedReader(fileReader)) { + MatcherUtil.read(reader, true, newToOld::addClass, newToOld::addField, newToOld::addMethod); + } + } + } + + private void readCounters(File counterFile) throws IOException { + Path counterPath = getExternalCounterFile(); + + if (counterPath != null && Files.exists(counterPath)) { + counterFile = counterPath.toFile(); + } + + try (FileReader fileReader = new FileReader(counterFile)) { + try (BufferedReader reader = new BufferedReader(fileReader)) { + String line; + + while ((line = reader.readLine()) != null) { + if (line.startsWith("# INTERMEDIARY-COUNTER")) { + String[] parts = line.split(" "); + counters.put(parts[2], Integer.parseInt(parts[3])); + } + } + } + } + } + + private void writeCounters(BufferedWriter writer) throws IOException { + StringJoiner counterLines = new StringJoiner("\n"); + + for (Map.Entry counter : counters.entrySet()) { + counterLines.add("# INTERMEDIARY-COUNTER " + counter.getKey() + " " + counter.getValue()); + } + + writer.write(counterLines.toString()); + Path counterPath = getExternalCounterFile(); + + if (counterPath != null) { + Files.write(counterPath, counterLines.toString().getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + } + } + + private Path getExternalCounterFile() { + if (System.getProperty("stitch.counter") != null) { + return Paths.get(System.getProperty("stitch.counter")); + } + + return null; + } } diff --git a/src/main/java/net/fabricmc/stitch/commands/tinyv2/CommandMergeTinyV2.java b/src/main/java/net/fabricmc/stitch/commands/tinyv2/CommandMergeTinyV2.java index 4257ec0..b6eba4f 100644 --- a/src/main/java/net/fabricmc/stitch/commands/tinyv2/CommandMergeTinyV2.java +++ b/src/main/java/net/fabricmc/stitch/commands/tinyv2/CommandMergeTinyV2.java @@ -43,34 +43,36 @@ * the same namespace as the first column and a different namespace as the second column. * The first column of the output will contain the shared namespace, * the second column of the output would be the second namespace of input a, - * and the third column of the output would be the second namespace of input b - *

- * Descriptors will remain as-is (using the namespace of the first column) - *

- *

- * For example: - *

- * Input A: - * intermediary named - * c net/minecraft/class_123 net/minecraft/somePackage/someClass - * m (Lnet/minecraft/class_124;)V method_1234 someMethod - *

- * Input B: - * intermediary official - * c net/minecraft/class_123 a - * m (Lnet/minecraft/class_124;)V method_1234 a - *

- * The output will be: - *

- * intermediary named official - * c net/minecraft/class_123 net/minecraft/somePackage/someClass a - * m (Lnet/minecraft/class_124;)V method_1234 someMethod a - *

+ * and the third column of the output would be the second namespace of input b. + * + *

Descriptors will remain as-is (using the namespace of the first column).

+ * + *

For example, for input A: + *


+ * intermediary	 named
+ * c	net/minecraft/class_123	net/minecraft/somePackage/someClass
+ * m	(Lnet/minecraft/class_124;)V	method_1234 someMethod
+ * 
+ * and input B: + *

+ * intermediary	official
+ * c	net/minecraft/class_123	a
+ * m	(Lnet/minecraft/class_124;)V	method_1234	a
+ * 
+ * the output will be: + *

+ * intermediary	named	official
+ * c	net/minecraft/class_123	net/minecraft/somePackage/someClass	a
+ * m	(Lnet/minecraft/class_124;)V	method_1234 someMethod	a
+ * 
+ *

+ * *

* After intermediary-named mappings are obtained, * and official-intermediary mappings are obtained and swapped using CommandReorderTinyV2, Loom merges them using this command, * and then reorders it to official-intermediary-named using CommandReorderTinyV2 again. * This is a convenient way of storing all the mappings in Loom. + *

*/ public class CommandMergeTinyV2 extends Command { public CommandMergeTinyV2() { @@ -78,7 +80,7 @@ public CommandMergeTinyV2() { } /** - * and are the tiny files to be merged. The result will be written to . + * {@code } and {@code } are the tiny files to be merged. The result will be written to {@code }. */ @Override public String getHelpString() { @@ -100,20 +102,21 @@ public void run(String[] args) throws IOException { TinyFile tinyFileB = TinyV2Reader.read(inputB); TinyHeader headerA = tinyFileA.getHeader(); TinyHeader headerB = tinyFileB.getHeader(); + if (headerA.getNamespaces().size() != 2) { throw new IllegalArgumentException(inputA + " must have exactly 2 namespaces."); } + if (headerB.getNamespaces().size() != 2) { throw new IllegalArgumentException(inputB + " must have exactly 2 namespaces."); } if (!headerA.getNamespaces().get(0).equals(headerB.getNamespaces().get(0))) { - throw new IllegalArgumentException( - String.format("The input tiny files must have the same namespaces as the first column. " + - "(%s has %s while %s has %s)", - inputA, headerA.getNamespaces().get(0), inputB, headerB.getNamespaces().get(0)) - ); + throw new IllegalArgumentException(String.format( + "The input tiny files must have the same namespaces as the first column. " + "(%s has %s while %s has %s)", + inputA, headerA.getNamespaces().get(0), inputB, headerB.getNamespaces().get(0))); } + System.out.println("Merging " + inputA + " with " + inputB); TinyFile mergedFile = merge(tinyFileA, tinyFileB); @@ -121,12 +124,10 @@ public void run(String[] args) throws IOException { System.out.println("Merged mappings written to " + Paths.get(args[2])); } - private TinyFile merge(TinyFile inputA, TinyFile inputB) { //TODO: how to merge properties? TinyHeader mergedHeader = mergeHeaders(inputA.getHeader(), inputB.getHeader()); - List keyUnion = keyUnion(inputA.getClassEntries(), inputB.getClassEntries()); Map inputAClasses = inputA.mapClassesByFirstNamespace(); @@ -153,29 +154,28 @@ private TinyClass matchEnclosingClassIfNeeded(String key, TinyClass tinyClass, M } /** - * Takes something like net/minecraft/class_123$class_124 that doesn't have a mapping, tries to find net/minecraft/class_123 - * , say the mapping of net/minecraft/class_123 is path/to/someclass and then returns a class of the form - * path/to/someclass$class124 + * Takes something like {@code net/minecraft/class_123$class_124} that doesn't have a mapping, tries to find {@code net/minecraft/class_123}. + * Say the mapping of {@code net/minecraft/class_123} is {@code path/to/someclass} and then returns a class of the form + * {@code path/to/someclass$class124} */ @Nonnull private String matchEnclosingClass(String sharedName, Map inputBClassBySharedNamespace) { String[] path = sharedName.split(escape("$")); int parts = path.length; + for (int i = parts - 2; i >= 0; i--) { String currentPath = String.join("$", Arrays.copyOfRange(path, 0, i + 1)); TinyClass match = inputBClassBySharedNamespace.get(currentPath); if (match != null && !match.getClassNames().get(1).isEmpty()) { return match.getClassNames().get(1) - + "$" + String.join("$", Arrays.copyOfRange(path, i + 1, path.length)); - + + "$" + String.join("$", Arrays.copyOfRange(path, i + 1, path.length)); } } return sharedName; } - private TinyClass mergeClasses(String sharedClassName, @Nonnull TinyClass classA, @Nonnull TinyClass classB) { List mergedNames = mergeNames(sharedClassName, classA, classB); List mergedComments = mergeComments(classA.getComments(), classB.getComments()); @@ -184,7 +184,7 @@ private TinyClass mergeClasses(String sharedClassName, @Nonnull TinyClass classA Map, TinyMethod> methodsA = classA.mapMethodsByFirstNamespaceAndDescriptor(); Map, TinyMethod> methodsB = classB.mapMethodsByFirstNamespaceAndDescriptor(); List mergedMethods = map(methodKeyUnion, - (Pair k) -> mergeMethods(k.getLeft(), methodsA.get(k), methodsB.get(k))); + (Pair k) -> mergeMethods(k.getLeft(), methodsA.get(k), methodsB.get(k))); List fieldKeyUnion = keyUnion(classA.getFields(), classB.getFields()); Map fieldsA = classA.mapFieldsByFirstNamespace(); @@ -194,8 +194,8 @@ private TinyClass mergeClasses(String sharedClassName, @Nonnull TinyClass classA return new TinyClass(mergedNames, mergedMethods, mergedFields, mergedComments); } - private static final TinyMethod EMPTY_METHOD = new TinyMethod(null, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); - + private static final TinyMethod EMPTY_METHOD = new TinyMethod(null, Collections.emptyList(), Collections.emptyList(), + Collections.emptyList(), Collections.emptyList()); private TinyMethod mergeMethods(String sharedMethodName, @Nullable TinyMethod methodA, @Nullable TinyMethod methodB) { List mergedNames = mergeNames(sharedMethodName, methodA, methodB); @@ -204,10 +204,9 @@ private TinyMethod mergeMethods(String sharedMethodName, @Nullable TinyMethod me List mergedComments = mergeComments(methodA.getComments(), methodB.getComments()); String descriptor = methodA.getMethodDescriptorInFirstNamespace() != null ? methodA.getMethodDescriptorInFirstNamespace() - : methodB.getMethodDescriptorInFirstNamespace(); + : methodB.getMethodDescriptorInFirstNamespace(); if (descriptor == null) throw new RuntimeException("no descriptor for key " + sharedMethodName); - //TODO: this won't work too well when the first namespace is named or there is more than one named namespace (hack) List mergedParameters = new ArrayList<>(); addParameters(methodA, mergedParameters, 2); @@ -233,18 +232,17 @@ private void addLocalVariables(TinyMethod method, List addTo, List names = new ArrayList<>(localVariable.getLocalVariableNames()); names.add(emptySpacePos, ""); addTo.add(new TinyLocalVariable(localVariable.getLvIndex(), localVariable.getLvStartOffset(), - localVariable.getLvTableIndex(), names, localVariable.getComments())); + localVariable.getLvTableIndex(), names, localVariable.getComments())); } } - private TinyField mergeFields(String sharedFieldName, @Nullable TinyField fieldA, @Nullable TinyField fieldB) { List mergedNames = mergeNames(sharedFieldName, fieldA, fieldB); List mergedComments = mergeComments(fieldA != null ? fieldA.getComments() : Collections.emptyList(), - fieldB != null ? fieldB.getComments() : Collections.emptyList()); + fieldB != null ? fieldB.getComments() : Collections.emptyList()); String descriptor = fieldA != null ? fieldA.getFieldDescriptorInFirstNamespace() - : fieldB != null ? fieldB.getFieldDescriptorInFirstNamespace() : null; + : fieldB != null ? fieldB.getFieldDescriptorInFirstNamespace() : null; if (descriptor == null) throw new RuntimeException("no descriptor for key " + sharedFieldName); return new TinyField(descriptor, mergedNames, mergedComments); @@ -269,7 +267,6 @@ private Stream> mapToFirstNamespaceAndDescriptor(TinyClass return tinyClass.getMethods().stream().map(m -> Pair.of(m.getMapping().get(0), m.getMethodDescriptorInFirstNamespace())); } - private List mergeNames(String key, @Nullable Mapping mappingA, @Nullable Mapping mappingB) { List merged = new ArrayList<>(); merged.add(key); @@ -303,5 +300,4 @@ private static String escape(String str) { private List map(List from, Function mapper) { return from.stream().map(mapper).collect(Collectors.toList()); } - } diff --git a/src/main/java/net/fabricmc/stitch/commands/tinyv2/CommandProposeV2FieldNames.java b/src/main/java/net/fabricmc/stitch/commands/tinyv2/CommandProposeV2FieldNames.java index 6e74d77..8b47b3c 100644 --- a/src/main/java/net/fabricmc/stitch/commands/tinyv2/CommandProposeV2FieldNames.java +++ b/src/main/java/net/fabricmc/stitch/commands/tinyv2/CommandProposeV2FieldNames.java @@ -24,14 +24,14 @@ import java.util.Map; import java.util.stream.Collectors; +import javax.annotation.Nullable; + import com.google.common.collect.Lists; import net.fabricmc.mappings.EntryTriple; import net.fabricmc.stitch.Command; import net.fabricmc.stitch.util.FieldNameFinder; -import javax.annotation.Nullable; - /** * Java stores the names of enums in the bytecode, and obfuscation doesn't get rid of it. We can use this for easy mappings. * This command adds all of the field mappings that FieldNameFinder finds (it overwrites existing mappings for those names). @@ -43,10 +43,12 @@ public CommandProposeV2FieldNames() { } /** - * is any Minecraft jar, and are mappings of that jar (the same version). - * with the additional field names will be written to .+ - * Assumes the input mappings are intermediary->yarn mappings! - * is a boolean ("true" or "false") deciding if existing yarn names should be replaced by the generated names. + *
    + *
  • {@code } is any Minecraft jar, and {@code } are mappings of that jar (the same version).
  • + *
  • {@code } with the additional field names will be written to {@code }.
  • + *
  • Assumes the input mappings are intermediary -> Yarn mappings!
  • + *
  • {@code } is a boolean ({@code true} or {@code false}) deciding if existing yarn names should be replaced by the generated names.
  • + *
*/ @Override public String getHelpString() { @@ -60,8 +62,8 @@ public boolean isArgumentCountValid(int count) { private Map generatedNamesOfClass(TinyClass tinyClass) { return tinyClass.getFields().stream().collect(Collectors.toMap( - (TinyField field) -> new EntryTriple(tinyClass.getClassNames().get(0), field.getFieldNames().get(0), field.getFieldDescriptorInFirstNamespace()) - , field -> field)); + (TinyField field) -> new EntryTriple(tinyClass.getClassNames().get(0), field.getFieldNames().get(0), + field.getFieldDescriptorInFirstNamespace()), field -> field)); } @Override @@ -72,10 +74,10 @@ public void run(String[] args) throws Exception { Boolean shouldReplace = parseBooleanOrNull(args[3]); // Validation - if(!inputJar.exists()) throw new IllegalArgumentException("Cannot find input jar at " + inputJar); - if(!Files.exists(inputMappings)) throw new IllegalArgumentException("Cannot find input mappings at " + inputMappings); - if(Files.exists(outputMappings)) System.out.println("Warning: existing file will be replaced by output mappings"); - if(shouldReplace == null) throw new IllegalArgumentException(" must be 'true' or 'false'"); + if (!inputJar.exists()) throw new IllegalArgumentException("Cannot find input jar at " + inputJar); + if (!Files.exists(inputMappings)) throw new IllegalArgumentException("Cannot find input mappings at " + inputMappings); + if (Files.exists(outputMappings)) System.out.println("Warning: existing file will be replaced by output mappings"); + if (shouldReplace == null) throw new IllegalArgumentException(" must be 'true' or 'false'"); // entrytriple from the input jar namespace Map generatedFieldNames = new FieldNameFinder().findNames(new File(args[0])); @@ -85,12 +87,13 @@ public void run(String[] args) throws Exception { Map fieldsMap = new HashMap<>(); tinyFile.getClassEntries().stream().map(this::generatedNamesOfClass).forEach(map -> map.forEach(fieldsMap::put)); Map classMap = tinyFile.mapClassesByFirstNamespace(); - int replaceCount = 0; + for (Map.Entry entry : generatedFieldNames.entrySet()) { EntryTriple key = entry.getKey(); String newName = entry.getValue(); TinyField field = fieldsMap.get(key); + // If the field name exists, replace the name with the auto-generated name, as long as is true. if (field != null) { if (shouldReplace) { @@ -99,13 +102,13 @@ public void run(String[] args) throws Exception { } } else { TinyClass tinyClass = classMap.get(key.getOwner()); + // If field name does not exist, but its class does exist, create a new mapping with the supplied generated name. if (tinyClass != null) { tinyClass.getFields().add(new TinyField(key.getDesc(), Lists.newArrayList(key.getName(), newName), Lists.newArrayList())); replaceCount++; } } - } System.err.println("Replaced " + replaceCount + " names in the mappings."); @@ -118,8 +121,13 @@ public void run(String[] args) throws Exception { @Nullable private Boolean parseBooleanOrNull(String booleanLiteral) { String lowerCase = booleanLiteral.toLowerCase(); - if (lowerCase.equals("true")) return Boolean.TRUE; - else if (lowerCase.equals("false")) return Boolean.FALSE; - else return null; + + if (lowerCase.equals("true")) { + return Boolean.TRUE; + } else if (lowerCase.equals("false")) { + return Boolean.FALSE; + } + + return null; } } diff --git a/src/main/java/net/fabricmc/stitch/commands/tinyv2/CommandReorderTinyV2.java b/src/main/java/net/fabricmc/stitch/commands/tinyv2/CommandReorderTinyV2.java index bcf1e36..1befa35 100644 --- a/src/main/java/net/fabricmc/stitch/commands/tinyv2/CommandReorderTinyV2.java +++ b/src/main/java/net/fabricmc/stitch/commands/tinyv2/CommandReorderTinyV2.java @@ -34,23 +34,29 @@ import net.fabricmc.stitch.Command; /** - * - Reorders the columns in the tiny file - * - Remaps the descriptors to use the newly first column. - *

- * For example: - *

- * This: - * intermediary named official - * c net/minecraft/class_123 net/minecraft/somePackage/someClass a - * m (Lnet/minecraft/class_124;)V method_1234 someMethod a - *

+ *

    + *
  • Reorders the columns in the tiny file
  • + *
  • Remaps the descriptors to use the newly first column
  • + *
+ * + *

For example, this: + *


+ * intermediary	named	official
+ * c	net/minecraft/class_123	net/minecraft/somePackage/someClass	a
+ * m	(Lnet/minecraft/class_124;)V	method_1234 someMethod	a
+ * 
* Reordered to official intermediary named: - * official intermediary named - * c a net/minecraft/class_123 net/minecraft/somePackage/someClass - * m (La;)V a method_1234 someMethod + *

+ * official	intermediary	named
+ * c	a	net/minecraft/class_123	net/minecraft/somePackage/someClass
+ * m	(La;)V	a	method_1234	someMethod
+ * 
+ *

+ * *

* This is used to reorder the the official-intermediary mappings to be intermediary-official, so they can be merged with * intermediary-named in CommandMergeTinyV2, and then reorder the outputted intermediary-official-named to official-intermediary-named. + *

*/ public class CommandReorderTinyV2 extends Command { public CommandReorderTinyV2() { @@ -58,8 +64,8 @@ public CommandReorderTinyV2() { } /** - * Reorders the columns in according to [new name order...] and puts the result in . - * new name order is for example "official intermediary named" + * Reorders the columns in {@code } according to {@code [new name order...]} and puts the result in {@code }. + * {@code new name order} is, for example, {@code official intermediary named}. */ @Override public String getHelpString() { @@ -81,8 +87,8 @@ public void run(String[] args) throws Exception { validateNamespaces(newOrder, tinyFile); Map mappingCopy = tinyFile.getClassEntries().stream() - .collect(Collectors.toMap(c -> c.getClassNames().get(0), - c -> new TinyClass(new ArrayList<>(c.getClassNames()), c.getMethods(), c.getFields(), c.getComments()))); + .collect(Collectors.toMap(c -> c.getClassNames().get(0), + c -> new TinyClass(new ArrayList<>(c.getClassNames()), c.getMethods(), c.getFields(), c.getComments()))); int newFirstNamespaceOldIndex = tinyFile.getHeader().getNamespaces().indexOf(newOrder.get(0)); reorder(tinyFile, newOrder); @@ -96,13 +102,14 @@ private void validateNamespaces(List newOrder, TinyFile tinyFile) { HashSet providedNamespacesOrderless = new HashSet<>(newOrder); if (!fileNamespacesOrderless.equals(providedNamespacesOrderless)) { - throw new IllegalArgumentException("The tiny file has different namespaces than those specified." + - " specified: " + providedNamespacesOrderless.toString() + ", file: " + fileNamespacesOrderless.toString()); + throw new IllegalArgumentException("The tiny file has different namespaces than those specified." + + " specified: " + providedNamespacesOrderless.toString() + ", file: " + fileNamespacesOrderless.toString()); } } private void reorder(TinyFile tinyFile, List newOrder) { Map indexMapping = new HashMap<>(); + for (int i = 0; i < newOrder.size(); i++) { indexMapping.put(tinyFile.getHeader().getNamespaces().indexOf(newOrder.get(i)), i); } @@ -112,7 +119,9 @@ private void reorder(TinyFile tinyFile, List newOrder) { for (int i = names.size(); i < newOrder.size(); i++) { names.add(""); } + List namesCopy = new ArrayList<>(names); + for (int i = 0; i < namesCopy.size(); i++) { names.set(indexMapping.get(i), namesCopy.get(i)); } @@ -124,29 +133,34 @@ private void remapDescriptors(TinyFile tinyFile, Map mappings for (TinyMethod method : tinyClass.getMethods()) { remapMethodDescriptor(method, mappings, targetNamespace); } + for (TinyField field : tinyClass.getFields()) { remapFieldDescriptor(field, mappings, targetNamespace); } } } - /** - * In this case the visitor is not a nice man and reorganizes the house as he sees fit + * In this case the visitor is not a nice man and reorganizes the house as he sees fit. */ private void visitNames(TinyFile tinyFile, Consumer> namesVisitor) { namesVisitor.accept(tinyFile.getHeader().getNamespaces()); + for (TinyClass tinyClass : tinyFile.getClassEntries()) { namesVisitor.accept(tinyClass.getClassNames()); + for (TinyMethod method : tinyClass.getMethods()) { namesVisitor.accept(method.getMethodNames()); + for (TinyMethodParameter parameter : method.getParameters()) { namesVisitor.accept(parameter.getParameterNames()); } + for (TinyLocalVariable localVariable : method.getLocalVariables()) { namesVisitor.accept(localVariable.getLocalVariableNames()); } } + for (TinyField field : tinyClass.getFields()) { namesVisitor.accept(field.getFieldNames()); } @@ -164,24 +178,22 @@ private void remapFieldDescriptor(TinyField field, Map mappin private void remapMethodDescriptor(TinyMethod method, Map mappings, int targetNamespace) { String descriptor = method.getMethodDescriptorInFirstNamespace(); String[] paramsAndReturnType = descriptor.split(Pattern.quote(")")); - if (paramsAndReturnType.length != 2) { - throw new IllegalArgumentException( - "method descriptor '" + descriptor + "' is of an unknown format."); - } + + if (paramsAndReturnType.length != 2) { + throw new IllegalArgumentException("method descriptor '" + descriptor + "' is of an unknown format."); + } + List params = parseParameterDescriptors(paramsAndReturnType[0].substring(1)); String returnType = paramsAndReturnType[1]; - List paramsMapped = params.stream().map(p -> remapType(p, mappings, targetNamespace)).collect(Collectors.toList()); String returnTypeMapped = returnType.equals("V") ? "V" : remapType(returnType, mappings, targetNamespace); - String newDescriptor = "(" + String.join("", paramsMapped) + ")" + returnTypeMapped; - method.setMethodDescriptorInFirstNamespace(newDescriptor); + method.setMethodDescriptorInFirstNamespace(newDescriptor); } private static final Collection primitiveTypeNames = Arrays.asList("B", "C", "D", "F", "I", "J", "S", "Z"); - private List parseParameterDescriptors(String concatenatedParameterDescriptors) { List parameterDescriptors = new ArrayList<>(); boolean inClassName = false; @@ -190,12 +202,14 @@ private List parseParameterDescriptors(String concatenatedParameterDescr for (int i = 0; i < concatenatedParameterDescriptors.length(); i++) { char c = concatenatedParameterDescriptors.charAt(i); + if (inClassName) { if (c == ';') { - if (currentClassName.length() == 0) { - throw new IllegalArgumentException( - "Empty class name in parameter list " + concatenatedParameterDescriptors + " at position " + i); - } + if (currentClassName.length() == 0) { + throw new IllegalArgumentException("Empty class name in parameter list " + concatenatedParameterDescriptors + + " at position " + i); + } + parameterDescriptors.add(Strings.repeat("[", inArrayNestingLevel) + "L" + currentClassName.toString() + ";"); inArrayNestingLevel = 0; currentClassName = new StringBuilder(); @@ -212,19 +226,15 @@ private List parseParameterDescriptors(String concatenatedParameterDescr } else if (c == 'L') { inClassName = true; } else { - throw new IllegalArgumentException( - "Unexpected special character " + c + " in parameter descriptor list " - + concatenatedParameterDescriptors); + throw new IllegalArgumentException("Unexpected special character " + c + " in parameter descriptor list " + + concatenatedParameterDescriptors); } } - } return parameterDescriptors; - } - /** * Remaps type from namespace X, to the namespace of targetNamespaceIndex in mappings, where mappings * is a mapping from names in namespace X to the names in all other namespaces. @@ -232,9 +242,11 @@ private List parseParameterDescriptors(String concatenatedParameterDescr private String remapType(String type, Map mappings, int targetNamespaceIndex) { if (type.isEmpty()) throw new IllegalArgumentException("types cannot be empty strings"); if (primitiveTypeNames.contains(type)) return type; - if (type.charAt(0) == '[') { - return "[" + remapType(type.substring(1), mappings, targetNamespaceIndex); - } + + if (type.charAt(0) == '[') { + return "[" + remapType(type.substring(1), mappings, targetNamespaceIndex); + } + if (type.charAt(0) == 'L' && type.charAt(type.length() - 1) == ';') { String className = type.substring(1, type.length() - 1); TinyClass mapping = mappings.get(className); @@ -243,8 +255,5 @@ private String remapType(String type, Map mappings, int targe } throw new IllegalArgumentException("type descriptor '" + type + "' is of an unknown format."); - } - - } diff --git a/src/main/java/net/fabricmc/stitch/commands/tinyv2/TinyClass.java b/src/main/java/net/fabricmc/stitch/commands/tinyv2/TinyClass.java index 2b7c9f6..88e22ca 100644 --- a/src/main/java/net/fabricmc/stitch/commands/tinyv2/TinyClass.java +++ b/src/main/java/net/fabricmc/stitch/commands/tinyv2/TinyClass.java @@ -28,7 +28,7 @@ public class TinyClass implements Comparable, Mapping { @Override public String toString() { return "TinyClass(names = [" + String.join(", ", classNames) + "], " + methods.size() + " methods, " - + fields.size() + " fields, " + comments.size() + " comments)"; + + fields.size() + " fields, " + comments.size() + " comments)"; } private final List classNames; @@ -50,7 +50,6 @@ public TinyClass(List classNames) { this.comments = new ArrayList<>(); } - /** * Descriptors are also taken into account because methods can overload. * The key format is firstMethodName + descriptor @@ -63,7 +62,6 @@ public Map mapFieldsByFirstNamespace() { return fields.stream().collect(Collectors.toMap(f -> f.getFieldNames().get(0), f -> f)); } - public List getClassNames() { return classNames; } diff --git a/src/main/java/net/fabricmc/stitch/commands/tinyv2/TinyField.java b/src/main/java/net/fabricmc/stitch/commands/tinyv2/TinyField.java index 543faa2..ef2c14d 100644 --- a/src/main/java/net/fabricmc/stitch/commands/tinyv2/TinyField.java +++ b/src/main/java/net/fabricmc/stitch/commands/tinyv2/TinyField.java @@ -20,7 +20,6 @@ import java.util.List; public class TinyField implements Comparable, Mapping { - /** * For example when we have official -> named mappings the descriptor will be in official, but in named -> official * the descriptor will be in named. diff --git a/src/main/java/net/fabricmc/stitch/commands/tinyv2/TinyHeader.java b/src/main/java/net/fabricmc/stitch/commands/tinyv2/TinyHeader.java index a6be00d..4585a18 100644 --- a/src/main/java/net/fabricmc/stitch/commands/tinyv2/TinyHeader.java +++ b/src/main/java/net/fabricmc/stitch/commands/tinyv2/TinyHeader.java @@ -20,11 +20,10 @@ import java.util.Map; public class TinyHeader { - private final List namespaces; private final int majorVersion; private final int minorVersion; - private final Map properties; + private final Map properties; public TinyHeader(List namespaces, int majorVersion, int minorVersion, Map properties) { this.namespaces = namespaces; diff --git a/src/main/java/net/fabricmc/stitch/commands/tinyv2/TinyLocalVariable.java b/src/main/java/net/fabricmc/stitch/commands/tinyv2/TinyLocalVariable.java index 56e0495..221b1d4 100644 --- a/src/main/java/net/fabricmc/stitch/commands/tinyv2/TinyLocalVariable.java +++ b/src/main/java/net/fabricmc/stitch/commands/tinyv2/TinyLocalVariable.java @@ -20,12 +20,9 @@ import java.util.List; public class TinyLocalVariable implements Comparable, Mapping { - private final int lvIndex; private final int lvStartOffset; - /** - * Will be -1 when there is no lvt index - */ + /** Will be -1 when there is no lvt index. */ private final int lvTableIndex; private final List localVariableNames; private final Collection comments; @@ -38,7 +35,6 @@ public TinyLocalVariable(int lvIndex, int lvStartOffset, int lvTableIndex, List< this.comments = comments; } - public int getLvIndex() { return lvIndex; } diff --git a/src/main/java/net/fabricmc/stitch/commands/tinyv2/TinyMethod.java b/src/main/java/net/fabricmc/stitch/commands/tinyv2/TinyMethod.java index 14c2796..608da98 100644 --- a/src/main/java/net/fabricmc/stitch/commands/tinyv2/TinyMethod.java +++ b/src/main/java/net/fabricmc/stitch/commands/tinyv2/TinyMethod.java @@ -22,7 +22,6 @@ import java.util.stream.Collectors; public class TinyMethod implements Comparable, Mapping { - @Override public String toString() { return "TinyMethod(names = [" + String.join(", ", methodNames) + "], desc = " + methodDescriptorInFirstNamespace @@ -79,7 +78,7 @@ public Collection getComments() { @Override public int compareTo(TinyMethod o) { return (methodNames.get(0) + methodDescriptorInFirstNamespace) - .compareTo(o.methodNames.get(0) + o.methodDescriptorInFirstNamespace); + .compareTo(o.methodNames.get(0) + o.methodDescriptorInFirstNamespace); } public void setMethodDescriptorInFirstNamespace(String methodDescriptorInFirstNamespace) { diff --git a/src/main/java/net/fabricmc/stitch/commands/tinyv2/TinyV2Reader.java b/src/main/java/net/fabricmc/stitch/commands/tinyv2/TinyV2Reader.java index 1d935d4..a498958 100644 --- a/src/main/java/net/fabricmc/stitch/commands/tinyv2/TinyV2Reader.java +++ b/src/main/java/net/fabricmc/stitch/commands/tinyv2/TinyV2Reader.java @@ -44,7 +44,7 @@ private enum CommentType { private TinyHeader header; private int namespaceAmount; - // private String + // private String private Set classes = new HashSet<>(); private TinyClass currentClass; @@ -61,8 +61,8 @@ private List getNames(MappingGetter getter) { @Override public void start(TinyMetadata metadata) { - header = new TinyHeader(new ArrayList<>(metadata.getNamespaces()), metadata.getMajorVersion(), metadata.getMinorVersion(), - metadata.getProperties()); + header = new TinyHeader(new ArrayList<>(metadata.getNamespaces()), metadata.getMajorVersion(), + metadata.getMinorVersion(), metadata.getProperties()); namespaceAmount = header.getNamespaces().size(); } @@ -82,35 +82,32 @@ public void pushField(MappingGetter name, String descriptor) { @Override public void pushMethod(MappingGetter name, String descriptor) { - currentMethod = new TinyMethod( - descriptor, getNames(name), new HashSet<>(), new HashSet<>(), new ArrayList<>() - ); + currentMethod = new TinyMethod(descriptor, getNames(name), new HashSet<>(), new HashSet<>(), new ArrayList<>()); currentClass.getMethods().add(currentMethod); currentCommentType = CommentType.METHOD; } @Override public void pushParameter(MappingGetter name, int localVariableIndex) { - currentParameter = new TinyMethodParameter( - localVariableIndex, getNames(name), new ArrayList<>() - ); + currentParameter = new TinyMethodParameter(localVariableIndex, getNames(name), new ArrayList<>()); currentMethod.getParameters().add(currentParameter); currentCommentType = CommentType.PARAMETER; } @Override public void pushLocalVariable(MappingGetter name, int localVariableIndex, int localVariableStartOffset, int localVariableTableIndex) { - currentLocalVariable = new TinyLocalVariable( - localVariableIndex, localVariableStartOffset, localVariableTableIndex, getNames(name), new ArrayList<>() - ); + currentLocalVariable = new TinyLocalVariable(localVariableIndex, localVariableStartOffset, + localVariableTableIndex, getNames(name), new ArrayList<>()); currentMethod.getLocalVariables().add(currentLocalVariable); currentCommentType = CommentType.LOCAL_VARIABLE; } @Override public void pushComment(String comment) { - if (inComment) + if (inComment) { throw new RuntimeException("commenting on comment"); + } + switch (currentCommentType) { case CLASS: currentClass.getComments().add(comment); @@ -130,6 +127,7 @@ public void pushComment(String comment) { default: throw new RuntimeException("unexpected comment without parent"); } + inComment = true; } @@ -143,19 +141,19 @@ public void pop(int count) { CommentType last = currentCommentType; switch (last) { - case CLASS: - currentCommentType = null; - break; - case FIELD: - case METHOD: - currentCommentType = CommentType.CLASS; - break; - case PARAMETER: - case LOCAL_VARIABLE: - currentCommentType = CommentType.METHOD; - break; - default: - throw new IllegalStateException("visit stack is empty!"); + case CLASS: + currentCommentType = null; + break; + case FIELD: + case METHOD: + currentCommentType = CommentType.CLASS; + break; + case PARAMETER: + case LOCAL_VARIABLE: + currentCommentType = CommentType.METHOD; + break; + default: + throw new IllegalStateException("visit stack is empty!"); } } } @@ -167,6 +165,7 @@ private TinyFile getAST() { public static TinyFile read(Path readFrom) throws IOException { Visitor visitor = new Visitor(); + try (BufferedReader reader = Files.newBufferedReader(readFrom)) { TinyV2Factory.visit(reader, visitor); } diff --git a/src/main/java/net/fabricmc/stitch/commands/tinyv2/TinyV2Writer.java b/src/main/java/net/fabricmc/stitch/commands/tinyv2/TinyV2Writer.java index c27de9a..b365a38 100644 --- a/src/main/java/net/fabricmc/stitch/commands/tinyv2/TinyV2Writer.java +++ b/src/main/java/net/fabricmc/stitch/commands/tinyv2/TinyV2Writer.java @@ -57,8 +57,6 @@ private Indents() { public static final int FIELD_COMMENT = 2; public static final int PARAMETER_COMMENT = 3; public static final int LOCAL_VARIABLE_COMMENT = 3; - - } private TinyV2Writer() { @@ -76,10 +74,9 @@ private void instanceWrite(TinyFile tinyFile, Path writeTo) throws IOException { } } - private void writeHeader(TinyHeader header) { writeLine(Indents.HEADER, header.getNamespaces(), Prefixes.HEADER, - Integer.toString(header.getMajorVersion()), Integer.toString(header.getMinorVersion())); + Integer.toString(header.getMajorVersion()), Integer.toString(header.getMinorVersion())); header.getProperties().forEach((key, value) -> writeLine(Indents.PROPERTY, value)); } @@ -90,7 +87,6 @@ private void writeClass(TinyClass tinyClass) { tinyClass.getMethods().stream().sorted().forEach(this::writeMethod); tinyClass.getFields().stream().sorted().forEach(this::writeField); - } private void writeMethod(TinyMethod method) { @@ -100,22 +96,20 @@ private void writeMethod(TinyMethod method) { method.getParameters().stream().sorted().forEach(this::writeMethodParameter); method.getLocalVariables().stream().sorted().forEach(this::writeLocalVariable); - } private void writeMethodParameter(TinyMethodParameter parameter) { writeLine(Indents.PARAMETER, parameter.getParameterNames(), Prefixes.PARAMETER, Integer.toString(parameter.getLvIndex())); + for (String comment : parameter.getComments()) { writeComment(Indents.PARAMETER_COMMENT, comment); } - } private void writeLocalVariable(TinyLocalVariable localVariable) { writeLine(Indents.LOCAL_VARIABLE, localVariable.getLocalVariableNames(), Prefixes.VARIABLE, - Integer.toString(localVariable.getLvIndex()), Integer.toString(localVariable.getLvStartOffset()), - Integer.toString(localVariable.getLvTableIndex()) - ); + Integer.toString(localVariable.getLvIndex()), Integer.toString(localVariable.getLvStartOffset()), + Integer.toString(localVariable.getLvTableIndex())); for (String comment : localVariable.getComments()) { writeComment(Indents.LOCAL_VARIABLE_COMMENT, comment); @@ -127,29 +121,30 @@ private void writeField(TinyField field) { for (String comment : field.getComments()) writeComment(Indents.FIELD_COMMENT, comment); } - private void writeComment(int indentLevel, String comment) { writeLine(indentLevel, Prefixes.COMMENT, escapeComment(comment)); } private static String escapeComment(String old) { StringBuilder sb = new StringBuilder(old.length()); + for (int i = 0; i < old.length(); i++) { char c = old.charAt(i); int t = TO_ESCAPE.indexOf(c); + if (t == -1) { sb.append(c); } else { sb.append('\\').append(ESCAPED.charAt(t)); } } + return sb.toString(); } private static final String TO_ESCAPE = "\\\n\r\0\t"; private static final String ESCAPED = "\\nr0t"; - private void write(int indentLevel, String... tabSeparatedStrings) { try { for (int i = 0; i < indentLevel; i++) writer.write('\t'); diff --git a/src/main/java/net/fabricmc/stitch/enigma/StitchEnigmaPlugin.java b/src/main/java/net/fabricmc/stitch/enigma/StitchEnigmaPlugin.java index 8a820ec..368b082 100644 --- a/src/main/java/net/fabricmc/stitch/enigma/StitchEnigmaPlugin.java +++ b/src/main/java/net/fabricmc/stitch/enigma/StitchEnigmaPlugin.java @@ -21,11 +21,9 @@ import cuchaz.enigma.api.service.ObfuscationTestService; public class StitchEnigmaPlugin implements EnigmaPlugin { - @Override public void init(EnigmaPluginContext ctx) { StitchNameProposalService.register(ctx); ctx.registerService("stitch:intermediary_obfuscation_test", ObfuscationTestService.TYPE, StitchIntermediaryObfuscationTestService::new); } - } diff --git a/src/main/java/net/fabricmc/stitch/enigma/StitchIntermediaryObfuscationTestService.java b/src/main/java/net/fabricmc/stitch/enigma/StitchIntermediaryObfuscationTestService.java index 4580a47..31a8b66 100644 --- a/src/main/java/net/fabricmc/stitch/enigma/StitchIntermediaryObfuscationTestService.java +++ b/src/main/java/net/fabricmc/stitch/enigma/StitchIntermediaryObfuscationTestService.java @@ -43,6 +43,7 @@ public boolean testDeobfuscated(Entry entry) { // all obfuscated components are, at their outermost, class_ String lastComponent = components[components.length - 1]; + if (lastComponent.startsWith(this.classPrefix) || lastComponent.startsWith(this.classPackagePrefix)) { return false; } diff --git a/src/main/java/net/fabricmc/stitch/enigma/StitchNameProposalService.java b/src/main/java/net/fabricmc/stitch/enigma/StitchNameProposalService.java index 52fda16..51e643b 100644 --- a/src/main/java/net/fabricmc/stitch/enigma/StitchNameProposalService.java +++ b/src/main/java/net/fabricmc/stitch/enigma/StitchNameProposalService.java @@ -16,23 +16,24 @@ package net.fabricmc.stitch.enigma; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + import cuchaz.enigma.analysis.index.JarIndex; import cuchaz.enigma.api.EnigmaPluginContext; import cuchaz.enigma.api.service.JarIndexerService; import cuchaz.enigma.api.service.NameProposalService; import cuchaz.enigma.classprovider.ClassProvider; import cuchaz.enigma.translation.representation.entry.FieldEntry; +import org.objectweb.asm.tree.MethodNode; + import net.fabricmc.mappings.EntryTriple; import net.fabricmc.stitch.util.FieldNameFinder; import net.fabricmc.stitch.util.NameFinderVisitor; import net.fabricmc.stitch.util.StitchUtil; -import org.objectweb.asm.tree.MethodNode; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; public class StitchNameProposalService { private Map fieldNames; @@ -41,7 +42,6 @@ private StitchNameProposalService(EnigmaPluginContext ctx) { ctx.registerService("stitch:jar_indexer", JarIndexerService.TYPE, ctx1 -> new JarIndexerService() { @Override public void acceptJar(Set classNames, ClassProvider classProvider, JarIndex jarIndex) { - Map> enumFields = new HashMap<>(); Map> methods = new HashMap<>(); @@ -58,11 +58,12 @@ public void acceptJar(Set classNames, ClassProvider classProvider, JarIn }); ctx.registerService("stitch:name_proposal", NameProposalService.TYPE, ctx12 -> (obfEntry, remapper) -> { - if(obfEntry instanceof FieldEntry){ + if (obfEntry instanceof FieldEntry) { FieldEntry fieldEntry = (FieldEntry) obfEntry; EntryTriple key = new EntryTriple(fieldEntry.getContainingClass().getFullName(), fieldEntry.getName(), fieldEntry.getDesc().toString()); return Optional.ofNullable(fieldNames.get(key)); } + return Optional.empty(); }); } diff --git a/src/main/java/net/fabricmc/stitch/merge/ClassMerger.java b/src/main/java/net/fabricmc/stitch/merge/ClassMerger.java index 738a195..a739845 100644 --- a/src/main/java/net/fabricmc/stitch/merge/ClassMerger.java +++ b/src/main/java/net/fabricmc/stitch/merge/ClassMerger.java @@ -16,210 +16,226 @@ package net.fabricmc.stitch.merge; -import net.fabricmc.stitch.util.StitchUtil; -import org.objectweb.asm.*; -import org.objectweb.asm.tree.*; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldNode; +import org.objectweb.asm.tree.InnerClassNode; +import org.objectweb.asm.tree.MethodNode; -import java.util.*; +import net.fabricmc.stitch.util.StitchUtil; public class ClassMerger { - private static final String SIDE_DESCRIPTOR = "Lnet/fabricmc/api/EnvType;"; - private static final String ITF_DESCRIPTOR = "Lnet/fabricmc/api/EnvironmentInterface;"; - private static final String ITF_LIST_DESCRIPTOR = "Lnet/fabricmc/api/EnvironmentInterfaces;"; - private static final String SIDED_DESCRIPTOR = "Lnet/fabricmc/api/Environment;"; - - private abstract class Merger { - private final Map entriesClient, entriesServer; - private final List entryNames; - - public Merger(List entriesClient, List entriesServer) { - this.entriesClient = new LinkedHashMap<>(); - this.entriesServer = new LinkedHashMap<>(); - - List listClient = toMap(entriesClient, this.entriesClient); - List listServer = toMap(entriesServer, this.entriesServer); - - this.entryNames = StitchUtil.mergePreserveOrder(listClient, listServer); - } - - public abstract String getName(T entry); - public abstract void applySide(T entry, String side); - - private final List toMap(List entries, Map map) { - List list = new ArrayList<>(entries.size()); - for (T entry : entries) { - String name = getName(entry); - map.put(name, entry); - list.add(name); - } - return list; - } - - public void merge(List list) { - for (String s : entryNames) { - T entryClient = entriesClient.get(s); - T entryServer = entriesServer.get(s); - - if (entryClient != null && entryServer != null) { - list.add(entryClient); - } else if (entryClient != null) { - applySide(entryClient, "CLIENT"); - list.add(entryClient); - } else { - applySide(entryServer, "SERVER"); - list.add(entryServer); - } - } - } - } - - private static void visitSideAnnotation(AnnotationVisitor av, String side) { - av.visitEnum("value", SIDE_DESCRIPTOR, side.toUpperCase(Locale.ROOT)); - av.visitEnd(); - } - - private static void visitItfAnnotation(AnnotationVisitor av, String side, List itfDescriptors) { - for (String itf : itfDescriptors) { - AnnotationVisitor avItf = av.visitAnnotation(null, ITF_DESCRIPTOR); - avItf.visitEnum("value", SIDE_DESCRIPTOR, side.toUpperCase(Locale.ROOT)); - avItf.visit("itf", Type.getType("L" + itf + ";")); - avItf.visitEnd(); - } - } - - public static class SidedClassVisitor extends ClassVisitor { - private final String side; - - public SidedClassVisitor(int api, ClassVisitor cv, String side) { - super(api, cv); - this.side = side; - } - - @Override - public void visitEnd() { - AnnotationVisitor av = cv.visitAnnotation(SIDED_DESCRIPTOR, true); - visitSideAnnotation(av, side); - super.visitEnd(); - } - } - - public ClassMerger() { - - } - - public byte[] merge(byte[] classClient, byte[] classServer) { - ClassReader readerC = new ClassReader(classClient); - ClassReader readerS = new ClassReader(classServer); - ClassWriter writer = new ClassWriter(0); - - ClassNode nodeC = new ClassNode(StitchUtil.ASM_VERSION); - readerC.accept(nodeC, 0); - - ClassNode nodeS = new ClassNode(StitchUtil.ASM_VERSION); - readerS.accept(nodeS, 0); - - ClassNode nodeOut = new ClassNode(StitchUtil.ASM_VERSION); - nodeOut.version = nodeC.version; - nodeOut.access = nodeC.access; - nodeOut.name = nodeC.name; - nodeOut.signature = nodeC.signature; - nodeOut.superName = nodeC.superName; - nodeOut.sourceFile = nodeC.sourceFile; - nodeOut.sourceDebug = nodeC.sourceDebug; - nodeOut.outerClass = nodeC.outerClass; - nodeOut.outerMethod = nodeC.outerMethod; - nodeOut.outerMethodDesc = nodeC.outerMethodDesc; - nodeOut.module = nodeC.module; - nodeOut.nestHostClass = nodeC.nestHostClass; - nodeOut.nestMembers = nodeC.nestMembers; - nodeOut.attrs = nodeC.attrs; - - if (nodeC.invisibleAnnotations != null) { - nodeOut.invisibleAnnotations = new ArrayList<>(); - nodeOut.invisibleAnnotations.addAll(nodeC.invisibleAnnotations); - } - if (nodeC.invisibleTypeAnnotations != null) { - nodeOut.invisibleTypeAnnotations = new ArrayList<>(); - nodeOut.invisibleTypeAnnotations.addAll(nodeC.invisibleTypeAnnotations); - } - if (nodeC.visibleAnnotations != null) { - nodeOut.visibleAnnotations = new ArrayList<>(); - nodeOut.visibleAnnotations.addAll(nodeC.visibleAnnotations); - } - if (nodeC.visibleTypeAnnotations != null) { - nodeOut.visibleTypeAnnotations = new ArrayList<>(); - nodeOut.visibleTypeAnnotations.addAll(nodeC.visibleTypeAnnotations); - } - - List itfs = StitchUtil.mergePreserveOrder(nodeC.interfaces, nodeS.interfaces); - nodeOut.interfaces = new ArrayList<>(); - - List clientItfs = new ArrayList<>(); - List serverItfs = new ArrayList<>(); - - for (String s : itfs) { - boolean nc = nodeC.interfaces.contains(s); - boolean ns = nodeS.interfaces.contains(s); - nodeOut.interfaces.add(s); - if (nc && !ns) { - clientItfs.add(s); - } else if (ns && !nc) { - serverItfs.add(s); - } - } - - if (!clientItfs.isEmpty() || !serverItfs.isEmpty()) { - AnnotationVisitor envInterfaces = nodeOut.visitAnnotation(ITF_LIST_DESCRIPTOR, false); - AnnotationVisitor eiArray = envInterfaces.visitArray("value"); - - if (!clientItfs.isEmpty()) { - visitItfAnnotation(eiArray, "CLIENT", clientItfs); - } - if (!serverItfs.isEmpty()) { - visitItfAnnotation(eiArray, "SERVER", serverItfs); - } - eiArray.visitEnd(); - envInterfaces.visitEnd(); - } - - new Merger(nodeC.innerClasses, nodeS.innerClasses) { - @Override - public String getName(InnerClassNode entry) { - return entry.name; - } - - @Override - public void applySide(InnerClassNode entry, String side) { - } - }.merge(nodeOut.innerClasses); - - new Merger(nodeC.fields, nodeS.fields) { - @Override - public String getName(FieldNode entry) { - return entry.name + ";;" + entry.desc; - } - - @Override - public void applySide(FieldNode entry, String side) { - AnnotationVisitor av = entry.visitAnnotation(SIDED_DESCRIPTOR, false); - visitSideAnnotation(av, side); - } - }.merge(nodeOut.fields); - - new Merger(nodeC.methods, nodeS.methods) { - @Override - public String getName(MethodNode entry) { - return entry.name + entry.desc; - } - - @Override - public void applySide(MethodNode entry, String side) { - AnnotationVisitor av = entry.visitAnnotation(SIDED_DESCRIPTOR, false); - visitSideAnnotation(av, side); - } - }.merge(nodeOut.methods); - - nodeOut.accept(writer); - return writer.toByteArray(); - } + private static final String SIDE_DESCRIPTOR = "Lnet/fabricmc/api/EnvType;"; + private static final String ITF_DESCRIPTOR = "Lnet/fabricmc/api/EnvironmentInterface;"; + private static final String ITF_LIST_DESCRIPTOR = "Lnet/fabricmc/api/EnvironmentInterfaces;"; + private static final String SIDED_DESCRIPTOR = "Lnet/fabricmc/api/Environment;"; + + private abstract class Merger { + private final Map entriesClient, entriesServer; + private final List entryNames; + + Merger(List entriesClient, List entriesServer) { + this.entriesClient = new LinkedHashMap<>(); + this.entriesServer = new LinkedHashMap<>(); + + List listClient = toMap(entriesClient, this.entriesClient); + List listServer = toMap(entriesServer, this.entriesServer); + + this.entryNames = StitchUtil.mergePreserveOrder(listClient, listServer); + } + + public abstract String getName(T entry); + public abstract void applySide(T entry, String side); + + private List toMap(List entries, Map map) { + List list = new ArrayList<>(entries.size()); + + for (T entry : entries) { + String name = getName(entry); + map.put(name, entry); + list.add(name); + } + + return list; + } + + public void merge(List list) { + for (String s : entryNames) { + T entryClient = entriesClient.get(s); + T entryServer = entriesServer.get(s); + + if (entryClient != null && entryServer != null) { + list.add(entryClient); + } else if (entryClient != null) { + applySide(entryClient, "CLIENT"); + list.add(entryClient); + } else { + applySide(entryServer, "SERVER"); + list.add(entryServer); + } + } + } + } + + private static void visitSideAnnotation(AnnotationVisitor av, String side) { + av.visitEnum("value", SIDE_DESCRIPTOR, side.toUpperCase(Locale.ROOT)); + av.visitEnd(); + } + + private static void visitItfAnnotation(AnnotationVisitor av, String side, List itfDescriptors) { + for (String itf : itfDescriptors) { + AnnotationVisitor avItf = av.visitAnnotation(null, ITF_DESCRIPTOR); + avItf.visitEnum("value", SIDE_DESCRIPTOR, side.toUpperCase(Locale.ROOT)); + avItf.visit("itf", Type.getType("L" + itf + ";")); + avItf.visitEnd(); + } + } + + public static class SidedClassVisitor extends ClassVisitor { + private final String side; + + public SidedClassVisitor(int api, ClassVisitor cv, String side) { + super(api, cv); + this.side = side; + } + + @Override + public void visitEnd() { + AnnotationVisitor av = cv.visitAnnotation(SIDED_DESCRIPTOR, true); + visitSideAnnotation(av, side); + super.visitEnd(); + } + } + + public byte[] merge(byte[] classClient, byte[] classServer) { + ClassReader readerC = new ClassReader(classClient); + ClassReader readerS = new ClassReader(classServer); + ClassWriter writer = new ClassWriter(0); + + ClassNode nodeC = new ClassNode(StitchUtil.ASM_VERSION); + readerC.accept(nodeC, 0); + + ClassNode nodeS = new ClassNode(StitchUtil.ASM_VERSION); + readerS.accept(nodeS, 0); + + ClassNode nodeOut = new ClassNode(StitchUtil.ASM_VERSION); + nodeOut.version = nodeC.version; + nodeOut.access = nodeC.access; + nodeOut.name = nodeC.name; + nodeOut.signature = nodeC.signature; + nodeOut.superName = nodeC.superName; + nodeOut.sourceFile = nodeC.sourceFile; + nodeOut.sourceDebug = nodeC.sourceDebug; + nodeOut.outerClass = nodeC.outerClass; + nodeOut.outerMethod = nodeC.outerMethod; + nodeOut.outerMethodDesc = nodeC.outerMethodDesc; + nodeOut.module = nodeC.module; + nodeOut.nestHostClass = nodeC.nestHostClass; + nodeOut.nestMembers = nodeC.nestMembers; + nodeOut.attrs = nodeC.attrs; + + if (nodeC.invisibleAnnotations != null) { + nodeOut.invisibleAnnotations = new ArrayList<>(); + nodeOut.invisibleAnnotations.addAll(nodeC.invisibleAnnotations); + } + + if (nodeC.invisibleTypeAnnotations != null) { + nodeOut.invisibleTypeAnnotations = new ArrayList<>(); + nodeOut.invisibleTypeAnnotations.addAll(nodeC.invisibleTypeAnnotations); + } + + if (nodeC.visibleAnnotations != null) { + nodeOut.visibleAnnotations = new ArrayList<>(); + nodeOut.visibleAnnotations.addAll(nodeC.visibleAnnotations); + } + + if (nodeC.visibleTypeAnnotations != null) { + nodeOut.visibleTypeAnnotations = new ArrayList<>(); + nodeOut.visibleTypeAnnotations.addAll(nodeC.visibleTypeAnnotations); + } + + List itfs = StitchUtil.mergePreserveOrder(nodeC.interfaces, nodeS.interfaces); + nodeOut.interfaces = new ArrayList<>(); + + List clientItfs = new ArrayList<>(); + List serverItfs = new ArrayList<>(); + + for (String s : itfs) { + boolean nc = nodeC.interfaces.contains(s); + boolean ns = nodeS.interfaces.contains(s); + nodeOut.interfaces.add(s); + + if (nc && !ns) { + clientItfs.add(s); + } else if (ns && !nc) { + serverItfs.add(s); + } + } + + if (!clientItfs.isEmpty() || !serverItfs.isEmpty()) { + AnnotationVisitor envInterfaces = nodeOut.visitAnnotation(ITF_LIST_DESCRIPTOR, false); + AnnotationVisitor eiArray = envInterfaces.visitArray("value"); + + if (!clientItfs.isEmpty()) { + visitItfAnnotation(eiArray, "CLIENT", clientItfs); + } + + if (!serverItfs.isEmpty()) { + visitItfAnnotation(eiArray, "SERVER", serverItfs); + } + + eiArray.visitEnd(); + envInterfaces.visitEnd(); + } + + new Merger(nodeC.innerClasses, nodeS.innerClasses) { + @Override + public String getName(InnerClassNode entry) { + return entry.name; + } + + @Override + public void applySide(InnerClassNode entry, String side) { + } + }.merge(nodeOut.innerClasses); + + new Merger(nodeC.fields, nodeS.fields) { + @Override + public String getName(FieldNode entry) { + return entry.name + ";;" + entry.desc; + } + + @Override + public void applySide(FieldNode entry, String side) { + AnnotationVisitor av = entry.visitAnnotation(SIDED_DESCRIPTOR, false); + visitSideAnnotation(av, side); + } + }.merge(nodeOut.fields); + + new Merger(nodeC.methods, nodeS.methods) { + @Override + public String getName(MethodNode entry) { + return entry.name + entry.desc; + } + + @Override + public void applySide(MethodNode entry, String side) { + AnnotationVisitor av = entry.visitAnnotation(SIDED_DESCRIPTOR, false); + visitSideAnnotation(av, side); + } + }.merge(nodeOut.methods); + + nodeOut.accept(writer); + return writer.toByteArray(); + } } diff --git a/src/main/java/net/fabricmc/stitch/merge/JarMerger.java b/src/main/java/net/fabricmc/stitch/merge/JarMerger.java index addf53b..94fc34c 100644 --- a/src/main/java/net/fabricmc/stitch/merge/JarMerger.java +++ b/src/main/java/net/fabricmc/stitch/merge/JarMerger.java @@ -16,211 +16,226 @@ package net.fabricmc.stitch.merge; -import net.fabricmc.stitch.util.SnowmanClassVisitor; -import net.fabricmc.stitch.util.StitchUtil; -import net.fabricmc.stitch.util.SyntheticParameterClassVisitor; -import org.objectweb.asm.ClassReader; -import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.ClassWriter; - -import java.io.*; +import java.io.File; +import java.io.IOException; import java.nio.charset.Charset; -import java.nio.file.*; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.StandardOpenOption; import java.nio.file.attribute.BasicFileAttributeView; import java.nio.file.attribute.BasicFileAttributes; -import java.util.*; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.TreeSet; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; + +import net.fabricmc.stitch.util.SnowmanClassVisitor; +import net.fabricmc.stitch.util.StitchUtil; +import net.fabricmc.stitch.util.SyntheticParameterClassVisitor; + public class JarMerger implements AutoCloseable { - public class Entry { - public final Path path; - public final BasicFileAttributes metadata; - public final byte[] data; - - public Entry(Path path, BasicFileAttributes metadata, byte[] data) { - this.path = path; - this.metadata = metadata; - this.data = data; - } - } - - private static final ClassMerger CLASS_MERGER = new ClassMerger(); - private final StitchUtil.FileSystemDelegate inputClientFs, inputServerFs, outputFs; - private final Path inputClient, inputServer; - private final Map entriesClient, entriesServer; - private final Set entriesAll; - private boolean removeSnowmen = false; - private boolean offsetSyntheticsParams = false; - - public JarMerger(File inputClient, File inputServer, File output) throws IOException { - if (output.exists()) { - if (!output.delete()) { - throw new IOException("Could not delete " + output.getName()); - } - } - - this.inputClient = (inputClientFs = StitchUtil.getJarFileSystem(inputClient, false)).get().getPath("/"); - this.inputServer = (inputServerFs = StitchUtil.getJarFileSystem(inputServer, false)).get().getPath("/"); - this.outputFs = StitchUtil.getJarFileSystem(output, true); - - this.entriesClient = new HashMap<>(); - this.entriesServer = new HashMap<>(); - this.entriesAll = new TreeSet<>(); - } - - public void enableSnowmanRemoval() { - removeSnowmen = true; - } - - public void enableSyntheticParamsOffset() { - offsetSyntheticsParams = true; - } - - @Override - public void close() throws IOException { - inputClientFs.close(); - inputServerFs.close(); - outputFs.close(); - } - - private void readToMap(Map map, Path input, boolean isServer) { - try { - Files.walkFileTree(input, new SimpleFileVisitor() { - @Override - public FileVisitResult visitFile(Path path, BasicFileAttributes attr) throws IOException { - if (attr.isDirectory()) { - return FileVisitResult.CONTINUE; - } - - if (!path.getFileName().toString().endsWith(".class")) { - if (path.toString().equals("/META-INF/MANIFEST.MF")) { - map.put("META-INF/MANIFEST.MF", new Entry(path, attr, - "Manifest-Version: 1.0\nMain-Class: net.minecraft.client.Main\n".getBytes(Charset.forName("UTF-8")))); - } else { - if (path.toString().startsWith("/META-INF/")) { - if (path.toString().endsWith(".SF") || path.toString().endsWith(".RSA")) { - return FileVisitResult.CONTINUE; - } - } - - map.put(path.toString().substring(1), new Entry(path, attr, null)); - } - - return FileVisitResult.CONTINUE; - } - - byte[] output = Files.readAllBytes(path); - map.put(path.toString().substring(1), new Entry(path, attr, output)); - return FileVisitResult.CONTINUE; - } - }); - } catch (IOException e) { - e.printStackTrace(); - } - } - - private void add(Entry entry) throws IOException { - Path outPath = outputFs.get().getPath(entry.path.toString()); - if (outPath.getParent() != null) { - Files.createDirectories(outPath.getParent()); - } - - if (entry.data != null) { - Files.write(outPath, entry.data, StandardOpenOption.CREATE_NEW); - } else { - Files.copy(entry.path, outPath); - } - - Files.getFileAttributeView(outPath, BasicFileAttributeView.class) - .setTimes( - entry.metadata.creationTime(), - entry.metadata.lastAccessTime(), - entry.metadata.lastModifiedTime() - ); - } - - public void merge() throws IOException { - ExecutorService service = Executors.newFixedThreadPool(2); - service.submit(() -> readToMap(entriesClient, inputClient, false)); - service.submit(() -> readToMap(entriesServer, inputServer, true)); - service.shutdown(); - try { - service.awaitTermination(1, TimeUnit.HOURS); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - entriesAll.addAll(entriesClient.keySet()); - entriesAll.addAll(entriesServer.keySet()); - - List entries = entriesAll.parallelStream().map((entry) -> { - boolean isClass = entry.endsWith(".class"); - boolean isMinecraft = entriesClient.containsKey(entry) || entry.startsWith("net/minecraft") || !entry.contains("/"); - Entry result; - String side = null; - - Entry entry1 = entriesClient.get(entry); - Entry entry2 = entriesServer.get(entry); - - if (entry1 != null && entry2 != null) { - if (Arrays.equals(entry1.data, entry2.data)) { - result = entry1; - } else { - if (isClass) { - result = new Entry(entry1.path, entry1.metadata, CLASS_MERGER.merge(entry1.data, entry2.data)); - } else { - // FIXME: More heuristics? - result = entry1; - } - } - } else if ((result = entry1) != null) { - side = "CLIENT"; - } else if ((result = entry2) != null) { - side = "SERVER"; - } - - if (isClass && !isMinecraft && "SERVER".equals(side)) { - // Server bundles libraries, client doesn't - skip them - return null; - } - - if (result != null) { - if (isMinecraft && isClass) { - byte[] data = result.data; - ClassReader reader = new ClassReader(data); - ClassWriter writer = new ClassWriter(0); - ClassVisitor visitor = writer; - - if (side != null) { - visitor = new ClassMerger.SidedClassVisitor(StitchUtil.ASM_VERSION, visitor, side); - } - - if (removeSnowmen) { - visitor = new SnowmanClassVisitor(StitchUtil.ASM_VERSION, visitor); - } - - if (offsetSyntheticsParams) { - visitor = new SyntheticParameterClassVisitor(StitchUtil.ASM_VERSION, visitor); - } - - if (visitor != writer) { - reader.accept(visitor, 0); - data = writer.toByteArray(); - result = new Entry(result.path, result.metadata, data); - } - } - - return result; - } else { - return null; - } - }).filter(Objects::nonNull).collect(Collectors.toList()); - - for (Entry e : entries) { - add(e); - } - } + public class Entry { + public final Path path; + public final BasicFileAttributes metadata; + public final byte[] data; + + public Entry(Path path, BasicFileAttributes metadata, byte[] data) { + this.path = path; + this.metadata = metadata; + this.data = data; + } + } + + private static final ClassMerger CLASS_MERGER = new ClassMerger(); + private final StitchUtil.FileSystemDelegate inputClientFs, inputServerFs, outputFs; + private final Path inputClient, inputServer; + private final Map entriesClient, entriesServer; + private final Set entriesAll; + private boolean removeSnowmen = false; + private boolean offsetSyntheticsParams = false; + + public JarMerger(File inputClient, File inputServer, File output) throws IOException { + if (output.exists()) { + if (!output.delete()) { + throw new IOException("Could not delete " + output.getName()); + } + } + + this.inputClient = (inputClientFs = StitchUtil.getJarFileSystem(inputClient, false)).get().getPath("/"); + this.inputServer = (inputServerFs = StitchUtil.getJarFileSystem(inputServer, false)).get().getPath("/"); + this.outputFs = StitchUtil.getJarFileSystem(output, true); + + this.entriesClient = new HashMap<>(); + this.entriesServer = new HashMap<>(); + this.entriesAll = new TreeSet<>(); + } + + public void enableSnowmanRemoval() { + removeSnowmen = true; + } + + public void enableSyntheticParamsOffset() { + offsetSyntheticsParams = true; + } + + @Override + public void close() throws IOException { + inputClientFs.close(); + inputServerFs.close(); + outputFs.close(); + } + + private void readToMap(Map map, Path input, boolean isServer) { + try { + Files.walkFileTree(input, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path path, BasicFileAttributes attr) throws IOException { + if (attr.isDirectory()) { + return FileVisitResult.CONTINUE; + } + + if (!path.getFileName().toString().endsWith(".class")) { + if (path.toString().equals("/META-INF/MANIFEST.MF")) { + map.put("META-INF/MANIFEST.MF", new Entry(path, attr, + "Manifest-Version: 1.0\nMain-Class: net.minecraft.client.Main\n".getBytes(Charset.forName("UTF-8")))); + } else { + if (path.toString().startsWith("/META-INF/")) { + if (path.toString().endsWith(".SF") || path.toString().endsWith(".RSA")) { + return FileVisitResult.CONTINUE; + } + } + + map.put(path.toString().substring(1), new Entry(path, attr, null)); + } + + return FileVisitResult.CONTINUE; + } + + byte[] output = Files.readAllBytes(path); + map.put(path.toString().substring(1), new Entry(path, attr, output)); + return FileVisitResult.CONTINUE; + } + }); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private void add(Entry entry) throws IOException { + Path outPath = outputFs.get().getPath(entry.path.toString()); + + if (outPath.getParent() != null) { + Files.createDirectories(outPath.getParent()); + } + + if (entry.data != null) { + Files.write(outPath, entry.data, StandardOpenOption.CREATE_NEW); + } else { + Files.copy(entry.path, outPath); + } + + Files.getFileAttributeView(outPath, BasicFileAttributeView.class) + .setTimes( + entry.metadata.creationTime(), + entry.metadata.lastAccessTime(), + entry.metadata.lastModifiedTime()); + } + + public void merge() throws IOException { + ExecutorService service = Executors.newFixedThreadPool(2); + service.submit(() -> readToMap(entriesClient, inputClient, false)); + service.submit(() -> readToMap(entriesServer, inputServer, true)); + service.shutdown(); + + try { + service.awaitTermination(1, TimeUnit.HOURS); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + entriesAll.addAll(entriesClient.keySet()); + entriesAll.addAll(entriesServer.keySet()); + + List entries = entriesAll.parallelStream().map((entry) -> { + boolean isClass = entry.endsWith(".class"); + boolean isMinecraft = entriesClient.containsKey(entry) + || entry.startsWith("net/minecraft") + || !entry.contains("/"); + Entry result; + String side = null; + + Entry entry1 = entriesClient.get(entry); + Entry entry2 = entriesServer.get(entry); + + if (entry1 != null && entry2 != null) { + if (Arrays.equals(entry1.data, entry2.data)) { + result = entry1; + } else { + if (isClass) { + result = new Entry(entry1.path, entry1.metadata, CLASS_MERGER.merge(entry1.data, entry2.data)); + } else { + // FIXME: More heuristics? + result = entry1; + } + } + } else if ((result = entry1) != null) { + side = "CLIENT"; + } else if ((result = entry2) != null) { + side = "SERVER"; + } + + if (isClass && !isMinecraft && "SERVER".equals(side)) { + // Server bundles libraries, client doesn't - skip them + return null; + } + + if (result != null) { + if (isMinecraft && isClass) { + byte[] data = result.data; + ClassReader reader = new ClassReader(data); + ClassWriter writer = new ClassWriter(0); + ClassVisitor visitor = writer; + + if (side != null) { + visitor = new ClassMerger.SidedClassVisitor(StitchUtil.ASM_VERSION, visitor, side); + } + + if (removeSnowmen) { + visitor = new SnowmanClassVisitor(StitchUtil.ASM_VERSION, visitor); + } + + if (offsetSyntheticsParams) { + visitor = new SyntheticParameterClassVisitor(StitchUtil.ASM_VERSION, visitor); + } + + if (visitor != writer) { + reader.accept(visitor, 0); + data = writer.toByteArray(); + result = new Entry(result.path, result.metadata, data); + } + } + + return result; + } else { + return null; + } + }).filter(Objects::nonNull).collect(Collectors.toList()); + + for (Entry e : entries) { + add(e); + } + } } diff --git a/src/main/java/net/fabricmc/stitch/representation/AbstractJarEntry.java b/src/main/java/net/fabricmc/stitch/representation/AbstractJarEntry.java index 9ff051a..8e6249a 100644 --- a/src/main/java/net/fabricmc/stitch/representation/AbstractJarEntry.java +++ b/src/main/java/net/fabricmc/stitch/representation/AbstractJarEntry.java @@ -17,41 +17,41 @@ package net.fabricmc.stitch.representation; public abstract class AbstractJarEntry { - protected String name; - protected int access; - - public AbstractJarEntry(String name) { - this.name = name; - } - - public int getAccess() { - return access; - } - - protected void setAccess(int value) { - this.access = value; - } - - public String getName() { - return name; - } - - protected String getKey() { - return name; - } - - @Override - public boolean equals(Object other) { - return other != null && other.getClass() == getClass() && ((AbstractJarEntry) other).getKey().equals(getKey()); - } - - @Override - public int hashCode() { - return getKey().hashCode(); - } - - @Override - public String toString() { - return getClass().getSimpleName() + "(" + getKey() + ")"; - } + protected String name; + protected int access; + + public AbstractJarEntry(String name) { + this.name = name; + } + + public int getAccess() { + return access; + } + + protected void setAccess(int value) { + this.access = value; + } + + public String getName() { + return name; + } + + protected String getKey() { + return name; + } + + @Override + public boolean equals(Object other) { + return other != null && other.getClass() == getClass() && ((AbstractJarEntry) other).getKey().equals(getKey()); + } + + @Override + public int hashCode() { + return getKey().hashCode(); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "(" + getKey() + ")"; + } } diff --git a/src/main/java/net/fabricmc/stitch/representation/Access.java b/src/main/java/net/fabricmc/stitch/representation/Access.java index 3b3088b..095303d 100644 --- a/src/main/java/net/fabricmc/stitch/representation/Access.java +++ b/src/main/java/net/fabricmc/stitch/representation/Access.java @@ -19,27 +19,26 @@ import org.objectweb.asm.Opcodes; public final class Access { - private Access() { + public static boolean isStatic(int access) { + return (access & Opcodes.ACC_STATIC) != 0; + } - } + public static boolean isPrivate(int access) { + return (access & Opcodes.ACC_PRIVATE) != 0; + } - public static boolean isStatic(int access) { - return (access & Opcodes.ACC_STATIC) != 0; - } + public static boolean isPrivateOrStatic(int access) { + return (access & (Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC)) != 0; + } - public static boolean isPrivate(int access) { - return (access & Opcodes.ACC_PRIVATE) != 0; - } + public static boolean isInterface(int access) { + return (access & Opcodes.ACC_INTERFACE) != 0; + } - public static boolean isPrivateOrStatic(int access) { - return (access & (Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC)) != 0; - } + public static boolean isNative(int access) { + return (access & (Opcodes.ACC_NATIVE)) != 0; + } - public static boolean isInterface(int access) { - return (access & Opcodes.ACC_INTERFACE) != 0; - } - - public static boolean isNative(int access) { - return (access & (Opcodes.ACC_NATIVE)) != 0; - } + private Access() { + } } diff --git a/src/main/java/net/fabricmc/stitch/representation/ClassPropagationTree.java b/src/main/java/net/fabricmc/stitch/representation/ClassPropagationTree.java index d46d13f..c9d0d68 100644 --- a/src/main/java/net/fabricmc/stitch/representation/ClassPropagationTree.java +++ b/src/main/java/net/fabricmc/stitch/representation/ClassPropagationTree.java @@ -16,9 +16,12 @@ package net.fabricmc.stitch.representation; -import net.fabricmc.stitch.util.StitchUtil; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedList; +import java.util.Set; -import java.util.*; +import net.fabricmc.stitch.util.StitchUtil; /** * TODO: This doesn't try to follow the JVM's logic at all. @@ -26,45 +29,48 @@ * where it could get away with naming them differently. */ public class ClassPropagationTree { - private final ClassStorage jar; - private final Set relevantClasses; - private final Set topmostClasses; + private final ClassStorage jar; + private final Set relevantClasses; + private final Set topmostClasses; + + public ClassPropagationTree(ClassStorage jar, JarClassEntry baseClass) { + this.jar = jar; + relevantClasses = StitchUtil.newIdentityHashSet(); + topmostClasses = StitchUtil.newIdentityHashSet(); + + LinkedList queue = new LinkedList<>(); + queue.add(baseClass); + + while (!queue.isEmpty()) { + JarClassEntry entry = queue.remove(); - public ClassPropagationTree(ClassStorage jar, JarClassEntry baseClass) { - this.jar = jar; - relevantClasses = StitchUtil.newIdentityHashSet(); - topmostClasses = StitchUtil.newIdentityHashSet(); + if (entry == null || relevantClasses.contains(entry)) { + continue; + } - LinkedList queue = new LinkedList<>(); - queue.add(baseClass); + relevantClasses.add(entry); + queue.addAll(entry.getSubclasses(jar)); + queue.addAll(entry.getImplementers(jar)); + int qSize = queue.size(); - while (!queue.isEmpty()) { - JarClassEntry entry = queue.remove(); - if (entry == null || relevantClasses.contains(entry)) { - continue; - } - relevantClasses.add(entry); + if (qSize == queue.size()) { + topmostClasses.add(entry); + } - int qSize = queue.size(); - queue.addAll(entry.getSubclasses(jar)); - queue.addAll(entry.getImplementers(jar)); - if (qSize == queue.size()) { - topmostClasses.add(entry); - } + queue.addAll(entry.getInterfaces(jar)); + JarClassEntry superClass = entry.getSuperClass(jar); - queue.addAll(entry.getInterfaces(jar)); - JarClassEntry superClass = entry.getSuperClass(jar); - if (superClass != null) { - queue.add(superClass); - } - } - } + if (superClass != null) { + queue.add(superClass); + } + } + } - public Collection getClasses() { - return Collections.unmodifiableSet(relevantClasses); - } + public Collection getClasses() { + return Collections.unmodifiableSet(relevantClasses); + } - public Collection getTopmostClasses() { - return Collections.unmodifiableSet(topmostClasses); - } + public Collection getTopmostClasses() { + return Collections.unmodifiableSet(topmostClasses); + } } diff --git a/src/main/java/net/fabricmc/stitch/representation/ClassStorage.java b/src/main/java/net/fabricmc/stitch/representation/ClassStorage.java index a06f72e..112ccc5 100644 --- a/src/main/java/net/fabricmc/stitch/representation/ClassStorage.java +++ b/src/main/java/net/fabricmc/stitch/representation/ClassStorage.java @@ -17,5 +17,5 @@ package net.fabricmc.stitch.representation; public interface ClassStorage { - JarClassEntry getClass(String name, boolean create); + JarClassEntry getClass(String name, boolean create); } diff --git a/src/main/java/net/fabricmc/stitch/representation/JarClassEntry.java b/src/main/java/net/fabricmc/stitch/representation/JarClassEntry.java index 90a0074..c2523f0 100644 --- a/src/main/java/net/fabricmc/stitch/representation/JarClassEntry.java +++ b/src/main/java/net/fabricmc/stitch/representation/JarClassEntry.java @@ -16,197 +16,209 @@ package net.fabricmc.stitch.representation; -import net.fabricmc.stitch.util.Pair; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.TreeMap; +import java.util.stream.Collectors; + import org.objectweb.asm.commons.Remapper; -import java.util.*; -import java.util.stream.Collectors; +import net.fabricmc.stitch.util.Pair; public class JarClassEntry extends AbstractJarEntry { - String fullyQualifiedName; - final Map innerClasses; - final Map fields; - final Map methods; - final Map>> relatedMethods; - - String signature; - String superclass; - List interfaces; - List subclasses; - List implementers; - - protected JarClassEntry(String name, String fullyQualifiedName) { - super(name); - - this.fullyQualifiedName = fullyQualifiedName; - this.innerClasses = new TreeMap<>(Comparator.naturalOrder()); - this.fields = new TreeMap<>(Comparator.naturalOrder()); - this.methods = new TreeMap<>(Comparator.naturalOrder()); - this.relatedMethods = new HashMap<>(); - - this.subclasses = new ArrayList<>(); - this.implementers = new ArrayList<>(); - } - - protected void populate(int access, String signature, String superclass, String[] interfaces) { - this.setAccess(access); - this.signature = signature; - this.superclass = superclass; - this.interfaces = Arrays.asList(interfaces); - } - - protected void populateParents(ClassStorage storage) { - JarClassEntry superEntry = getSuperClass(storage); - if (superEntry != null) { - superEntry.subclasses.add(fullyQualifiedName); - } - - for (JarClassEntry itf : getInterfaces(storage)) { - if (itf != null) { - itf.implementers.add(fullyQualifiedName); - } - } - } - - // unstable - public Collection> getRelatedMethods(JarMethodEntry m) { - //noinspection unchecked - return relatedMethods.getOrDefault(m.getKey(), Collections.EMPTY_SET); - } - - public String getFullyQualifiedName() { - return fullyQualifiedName; - } - - public String getSignature() { - return signature; - } - - public String getSuperClassName() { - return superclass; - } - - public JarClassEntry getSuperClass(ClassStorage storage) { - return storage.getClass(superclass, false); - } - - public List getInterfaceNames() { - return Collections.unmodifiableList(interfaces); - } - - public List getInterfaces(ClassStorage storage) { - return toClassEntryList(storage, interfaces); - } - - public List getSubclassNames() { - return Collections.unmodifiableList(subclasses); - } - - public List getSubclasses(ClassStorage storage) { - return toClassEntryList(storage, subclasses); - } - - public List getImplementerNames() { - return Collections.unmodifiableList(implementers); - } - - public List getImplementers(ClassStorage storage) { - return toClassEntryList(storage, implementers); - } - - private List toClassEntryList(ClassStorage storage, List stringList) { - if (stringList == null) { - return Collections.emptyList(); - } - - return stringList.stream() - .map((s) -> storage.getClass(s, false)) - .filter(Objects::nonNull) - .collect(Collectors.toList()); - } - - public JarClassEntry getInnerClass(String name) { - return innerClasses.get(name); - } - - public JarFieldEntry getField(String name) { - return fields.get(name); - } - - public JarMethodEntry getMethod(String name) { - return methods.get(name); - } - - public Collection getInnerClasses() { - return innerClasses.values(); - } - - public Collection getFields() { - return fields.values(); - } - - public Collection getMethods() { - return methods.values(); - } - - public boolean isInterface() { - return Access.isInterface(getAccess()); - } - - public boolean isAnonymous() { - return getName().matches("[0-9]+"); - } - - @Override - public String getKey() { - return getFullyQualifiedName(); - } - - public void remap(Remapper remapper) { - String oldName = fullyQualifiedName; - fullyQualifiedName = remapper.map(fullyQualifiedName); - String[] s = fullyQualifiedName.split("\\$"); - name = s[s.length - 1]; - - if (superclass != null) { - superclass = remapper.map(superclass); - } - - interfaces = interfaces.stream().map(remapper::map).collect(Collectors.toList()); - subclasses = subclasses.stream().map(remapper::map).collect(Collectors.toList()); - implementers = implementers.stream().map(remapper::map).collect(Collectors.toList()); - - Map innerClassOld = new HashMap<>(innerClasses); - Map fieldsOld = new HashMap<>(fields); - Map methodsOld = new HashMap<>(methods); - Map methodKeyRemaps = new HashMap<>(); - - innerClasses.clear(); - fields.clear(); - methods.clear(); - - for (Map.Entry entry : innerClassOld.entrySet()) { - entry.getValue().remap(remapper); - innerClasses.put(entry.getValue().name, entry.getValue()); - } - - for (Map.Entry entry : fieldsOld.entrySet()) { - entry.getValue().remap(this, oldName, remapper); - fields.put(entry.getValue().getKey(), entry.getValue()); - } - - for (Map.Entry entry : methodsOld.entrySet()) { - entry.getValue().remap(this, oldName, remapper); - methods.put(entry.getValue().getKey(), entry.getValue()); - methodKeyRemaps.put(entry.getKey(), entry.getValue().getKey()); - } - - // TODO: remap relatedMethods strings??? - Map>> relatedMethodsOld = new HashMap<>(relatedMethods); - relatedMethods.clear(); - - for (Map.Entry>> entry : relatedMethodsOld.entrySet()) { - relatedMethods.put(methodKeyRemaps.getOrDefault(entry.getKey(), entry.getKey()), entry.getValue()); - } - } + String fullyQualifiedName; + final Map innerClasses; + final Map fields; + final Map methods; + final Map>> relatedMethods; + + String signature; + String superclass; + List interfaces; + List subclasses; + List implementers; + + protected JarClassEntry(String name, String fullyQualifiedName) { + super(name); + + this.fullyQualifiedName = fullyQualifiedName; + this.innerClasses = new TreeMap<>(Comparator.naturalOrder()); + this.fields = new TreeMap<>(Comparator.naturalOrder()); + this.methods = new TreeMap<>(Comparator.naturalOrder()); + this.relatedMethods = new HashMap<>(); + + this.subclasses = new ArrayList<>(); + this.implementers = new ArrayList<>(); + } + + protected void populate(int access, String signature, String superclass, String[] interfaces) { + this.setAccess(access); + this.signature = signature; + this.superclass = superclass; + this.interfaces = Arrays.asList(interfaces); + } + + protected void populateParents(ClassStorage storage) { + JarClassEntry superEntry = getSuperClass(storage); + + if (superEntry != null) { + superEntry.subclasses.add(fullyQualifiedName); + } + + for (JarClassEntry itf : getInterfaces(storage)) { + if (itf != null) { + itf.implementers.add(fullyQualifiedName); + } + } + } + + // unstable + public Collection> getRelatedMethods(JarMethodEntry m) { + //noinspection unchecked + return relatedMethods.getOrDefault(m.getKey(), Collections.EMPTY_SET); + } + + public String getFullyQualifiedName() { + return fullyQualifiedName; + } + + public String getSignature() { + return signature; + } + + public String getSuperClassName() { + return superclass; + } + + public JarClassEntry getSuperClass(ClassStorage storage) { + return storage.getClass(superclass, false); + } + + public List getInterfaceNames() { + return Collections.unmodifiableList(interfaces); + } + + public List getInterfaces(ClassStorage storage) { + return toClassEntryList(storage, interfaces); + } + + public List getSubclassNames() { + return Collections.unmodifiableList(subclasses); + } + + public List getSubclasses(ClassStorage storage) { + return toClassEntryList(storage, subclasses); + } + + public List getImplementerNames() { + return Collections.unmodifiableList(implementers); + } + + public List getImplementers(ClassStorage storage) { + return toClassEntryList(storage, implementers); + } + + private List toClassEntryList(ClassStorage storage, List stringList) { + if (stringList == null) { + return Collections.emptyList(); + } + + return stringList.stream() + .map((s) -> storage.getClass(s, false)) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + public JarClassEntry getInnerClass(String name) { + return innerClasses.get(name); + } + + public JarFieldEntry getField(String name) { + return fields.get(name); + } + + public JarMethodEntry getMethod(String name) { + return methods.get(name); + } + + public Collection getInnerClasses() { + return innerClasses.values(); + } + + public Collection getFields() { + return fields.values(); + } + + public Collection getMethods() { + return methods.values(); + } + + public boolean isInterface() { + return Access.isInterface(getAccess()); + } + + public boolean isAnonymous() { + return getName().matches("[0-9]+"); + } + + @Override + public String getKey() { + return getFullyQualifiedName(); + } + + public void remap(Remapper remapper) { + String oldName = fullyQualifiedName; + fullyQualifiedName = remapper.map(fullyQualifiedName); + String[] s = fullyQualifiedName.split("\\$"); + name = s[s.length - 1]; + + if (superclass != null) { + superclass = remapper.map(superclass); + } + + interfaces = interfaces.stream().map(remapper::map).collect(Collectors.toList()); + subclasses = subclasses.stream().map(remapper::map).collect(Collectors.toList()); + implementers = implementers.stream().map(remapper::map).collect(Collectors.toList()); + + Map innerClassOld = new HashMap<>(innerClasses); + Map fieldsOld = new HashMap<>(fields); + Map methodsOld = new HashMap<>(methods); + Map methodKeyRemaps = new HashMap<>(); + + innerClasses.clear(); + fields.clear(); + methods.clear(); + + for (Map.Entry entry : innerClassOld.entrySet()) { + entry.getValue().remap(remapper); + innerClasses.put(entry.getValue().name, entry.getValue()); + } + + for (Map.Entry entry : fieldsOld.entrySet()) { + entry.getValue().remap(this, oldName, remapper); + fields.put(entry.getValue().getKey(), entry.getValue()); + } + + for (Map.Entry entry : methodsOld.entrySet()) { + entry.getValue().remap(this, oldName, remapper); + methods.put(entry.getValue().getKey(), entry.getValue()); + methodKeyRemaps.put(entry.getKey(), entry.getValue().getKey()); + } + + // TODO: remap relatedMethods strings??? + Map>> relatedMethodsOld = new HashMap<>(relatedMethods); + relatedMethods.clear(); + + for (Map.Entry>> entry : relatedMethodsOld.entrySet()) { + relatedMethods.put(methodKeyRemaps.getOrDefault(entry.getKey(), entry.getKey()), entry.getValue()); + } + } } diff --git a/src/main/java/net/fabricmc/stitch/representation/JarFieldEntry.java b/src/main/java/net/fabricmc/stitch/representation/JarFieldEntry.java index 9577cd8..52fd2c7 100644 --- a/src/main/java/net/fabricmc/stitch/representation/JarFieldEntry.java +++ b/src/main/java/net/fabricmc/stitch/representation/JarFieldEntry.java @@ -19,33 +19,33 @@ import org.objectweb.asm.commons.Remapper; public class JarFieldEntry extends AbstractJarEntry { - protected String desc; - protected String signature; - - JarFieldEntry(int access, String name, String desc, String signature) { - super(name); - this.setAccess(access); - this.desc = desc; - this.signature = signature; - } - - public String getDescriptor() { - return desc; - } - - public String getSignature() { - return signature; - } - - @Override - protected String getKey() { - return super.getKey() + desc; - } - - public void remap(JarClassEntry classEntry, String oldOwner, Remapper remapper) { - String pastDesc = desc; - - name = remapper.mapFieldName(oldOwner, name, pastDesc); - desc = remapper.mapDesc(pastDesc); - } + protected String desc; + protected String signature; + + JarFieldEntry(int access, String name, String desc, String signature) { + super(name); + this.setAccess(access); + this.desc = desc; + this.signature = signature; + } + + public String getDescriptor() { + return desc; + } + + public String getSignature() { + return signature; + } + + @Override + protected String getKey() { + return super.getKey() + desc; + } + + public void remap(JarClassEntry classEntry, String oldOwner, Remapper remapper) { + String pastDesc = desc; + + name = remapper.mapFieldName(oldOwner, name, pastDesc); + desc = remapper.mapDesc(pastDesc); + } } diff --git a/src/main/java/net/fabricmc/stitch/representation/JarMethodEntry.java b/src/main/java/net/fabricmc/stitch/representation/JarMethodEntry.java index ce45fae..123ff8d 100644 --- a/src/main/java/net/fabricmc/stitch/representation/JarMethodEntry.java +++ b/src/main/java/net/fabricmc/stitch/representation/JarMethodEntry.java @@ -16,111 +16,120 @@ package net.fabricmc.stitch.representation; -import net.fabricmc.stitch.util.StitchUtil; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; + import org.objectweb.asm.commons.Remapper; -import java.util.*; +import net.fabricmc.stitch.util.StitchUtil; public class JarMethodEntry extends AbstractJarEntry { - protected String desc; - protected String signature; - - protected JarMethodEntry(int access, String name, String desc, String signature) { - super(name); - this.setAccess(access); - this.desc = desc; - this.signature = signature; - } - - public String getDescriptor() { - return desc; - } - - public String getSignature() { - return signature; - } - - @Override - protected String getKey() { - return super.getKey() + desc; - } - - public boolean isSource(ClassStorage storage, JarClassEntry c) { - if (Access.isPrivateOrStatic(getAccess())) { - return true; - } - - Set entries = StitchUtil.newIdentityHashSet(); - entries.add(c); - getMatchingSources(entries, storage, c); - return entries.size() == 1; - } - - public List getMatchingEntries(ClassStorage storage, JarClassEntry c) { - if (Access.isPrivateOrStatic(getAccess())) { - return Collections.singletonList(c); - } - - Set entries = StitchUtil.newIdentityHashSet(); - Set entriesNew = StitchUtil.newIdentityHashSet(); - entries.add(c); - int lastSize = 0; - - while (entries.size() > lastSize) { - lastSize = entries.size(); - - for (JarClassEntry cc : entries) { - getMatchingSources(entriesNew, storage, cc); - } - entries.addAll(entriesNew); - entriesNew.clear(); - - for (JarClassEntry cc : entries) { - getMatchingEntries(entriesNew, storage, cc, 0); - } - entries.addAll(entriesNew); - entriesNew.clear(); - } - - entries.removeIf(cc -> cc.getMethod(getKey()) == null); - - return new ArrayList<>(entries); - } - - void getMatchingSources(Collection entries, ClassStorage storage, JarClassEntry c) { - JarMethodEntry m = c.getMethod(getKey()); - if (m != null) { - if (!Access.isPrivateOrStatic(m.getAccess())) { - entries.add(c); - } - } - - JarClassEntry superClass = c.getSuperClass(storage); - if (superClass != null) { - getMatchingSources(entries, storage, superClass); - } - - for (JarClassEntry itf : c.getInterfaces(storage)) { - getMatchingSources(entries, storage, itf); - } - } - - void getMatchingEntries(Collection entries, ClassStorage storage, JarClassEntry c, int indent) { - entries.add(c); - - for (JarClassEntry cc : c.getSubclasses(storage)) { - getMatchingEntries(entries, storage, cc, indent + 1); - } - - for (JarClassEntry cc : c.getImplementers(storage)) { - getMatchingEntries(entries, storage, cc, indent + 1); - } - } - - public void remap(JarClassEntry classEntry, String oldOwner, Remapper remapper) { - String pastDesc = desc; - - name = remapper.mapMethodName(oldOwner, name, pastDesc); - desc = remapper.mapMethodDesc(pastDesc); - } -} \ No newline at end of file + protected String desc; + protected String signature; + + protected JarMethodEntry(int access, String name, String desc, String signature) { + super(name); + this.setAccess(access); + this.desc = desc; + this.signature = signature; + } + + public String getDescriptor() { + return desc; + } + + public String getSignature() { + return signature; + } + + @Override + protected String getKey() { + return super.getKey() + desc; + } + + public boolean isSource(ClassStorage storage, JarClassEntry c) { + if (Access.isPrivateOrStatic(getAccess())) { + return true; + } + + Set entries = StitchUtil.newIdentityHashSet(); + entries.add(c); + getMatchingSources(entries, storage, c); + return entries.size() == 1; + } + + public List getMatchingEntries(ClassStorage storage, JarClassEntry c) { + if (Access.isPrivateOrStatic(getAccess())) { + return Collections.singletonList(c); + } + + Set entries = StitchUtil.newIdentityHashSet(); + Set entriesNew = StitchUtil.newIdentityHashSet(); + entries.add(c); + int lastSize = 0; + + while (entries.size() > lastSize) { + lastSize = entries.size(); + + for (JarClassEntry cc : entries) { + getMatchingSources(entriesNew, storage, cc); + } + + entries.addAll(entriesNew); + entriesNew.clear(); + + for (JarClassEntry cc : entries) { + getMatchingEntries(entriesNew, storage, cc, 0); + } + + entries.addAll(entriesNew); + entriesNew.clear(); + } + + entries.removeIf(cc -> cc.getMethod(getKey()) == null); + + return new ArrayList<>(entries); + } + + void getMatchingSources(Collection entries, ClassStorage storage, JarClassEntry c) { + JarMethodEntry m = c.getMethod(getKey()); + + if (m != null) { + if (!Access.isPrivateOrStatic(m.getAccess())) { + entries.add(c); + } + } + + JarClassEntry superClass = c.getSuperClass(storage); + + if (superClass != null) { + getMatchingSources(entries, storage, superClass); + } + + for (JarClassEntry itf : c.getInterfaces(storage)) { + getMatchingSources(entries, storage, itf); + } + } + + void getMatchingEntries(Collection entries, ClassStorage storage, JarClassEntry c, int indent) { + entries.add(c); + + for (JarClassEntry cc : c.getSubclasses(storage)) { + getMatchingEntries(entries, storage, cc, indent + 1); + } + + for (JarClassEntry cc : c.getImplementers(storage)) { + getMatchingEntries(entries, storage, cc, indent + 1); + } + } + + public void remap(JarClassEntry classEntry, String oldOwner, Remapper remapper) { + String pastDesc = desc; + + name = remapper.mapMethodName(oldOwner, name, pastDesc); + desc = remapper.mapMethodDesc(pastDesc); + } +} diff --git a/src/main/java/net/fabricmc/stitch/representation/JarReader.java b/src/main/java/net/fabricmc/stitch/representation/JarReader.java index 4f4c048..886b0cb 100644 --- a/src/main/java/net/fabricmc/stitch/representation/JarReader.java +++ b/src/main/java/net/fabricmc/stitch/representation/JarReader.java @@ -16,293 +16,305 @@ package net.fabricmc.stitch.representation; -import net.fabricmc.stitch.util.StitchUtil; -import org.objectweb.asm.*; -import org.objectweb.asm.commons.Remapper; - import java.io.FileInputStream; import java.io.IOException; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.jar.JarInputStream; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.commons.Remapper; + +import net.fabricmc.stitch.util.StitchUtil; + public class JarReader { - public static class Builder { - private final JarReader reader; - - private Builder(JarReader reader) { - this.reader = reader; - } - - public static Builder create(JarRootEntry jar) { - return new Builder(new JarReader(jar)); - } - - public Builder joinMethodEntries(boolean value) { - reader.joinMethodEntries = value; - return this; - } - - public Builder withRemapper(Remapper remapper) { - reader.remapper = remapper; - return this; - } - - public JarReader build() { - return reader; - } - } - - private final JarRootEntry jar; - private boolean joinMethodEntries = true; - private Remapper remapper; - - public JarReader(JarRootEntry jar) { - this.jar = jar; - } - - private class VisitorClass extends ClassVisitor { - private JarClassEntry entry; - - public VisitorClass(int api, ClassVisitor classVisitor) { - super(api, classVisitor); - } - - @Override - public void visit(final int version, final int access, final String name, final String signature, - final String superName, final String[] interfaces) { - this.entry = jar.getClass(name, true); - this.entry.populate(access, signature, superName, interfaces); - - super.visit(version, access, name, signature, superName, interfaces); - } - - @Override - public FieldVisitor visitField(final int access, final String name, final String descriptor, - final String signature, final Object value) { - JarFieldEntry field = new JarFieldEntry(access, name, descriptor, signature); - this.entry.fields.put(field.getKey(), field); - - return new VisitorField(api, super.visitField(access, name, descriptor, signature, value), - entry, field); - } - - @Override - public MethodVisitor visitMethod(final int access, final String name, final String descriptor, - final String signature, final String[] exceptions) { - JarMethodEntry method = new JarMethodEntry(access, name, descriptor, signature); - this.entry.methods.put(method.getKey(), method); - - return new VisitorMethod(api, super.visitMethod(access, name, descriptor, signature, exceptions), - entry, method); - } - } - - private class VisitorClassStageTwo extends ClassVisitor { - private JarClassEntry entry; - - public VisitorClassStageTwo(int api, ClassVisitor classVisitor) { - super(api, classVisitor); - } - - @Override - public void visit(final int version, final int access, final String name, final String signature, - final String superName, final String[] interfaces) { - this.entry = jar.getClass(name, true); - super.visit(version, access, name, signature, superName, interfaces); - } - - @Override - public MethodVisitor visitMethod(final int access, final String name, final String descriptor, - final String signature, final String[] exceptions) { - JarMethodEntry method = new JarMethodEntry(access, name, descriptor, signature); - this.entry.methods.put(method.getKey(), method); - - if ((access & (Opcodes.ACC_BRIDGE | Opcodes.ACC_SYNTHETIC)) != 0) { - return new VisitorBridge(api, access, super.visitMethod(access, name, descriptor, signature, exceptions), - entry, method); - } else { - return super.visitMethod(access, name, descriptor, signature, exceptions); - } - } - } - - private class VisitorField extends FieldVisitor { - private final JarClassEntry classEntry; - private final JarFieldEntry entry; - - public VisitorField(int api, FieldVisitor fieldVisitor, JarClassEntry classEntry, JarFieldEntry entry) { - super(api, fieldVisitor); - this.classEntry = classEntry; - this.entry = entry; - } - } - - private static class MethodRef { - final String owner, name, descriptor; - - MethodRef(String owner, String name, String descriptor) { - this.owner = owner; - this.name = name; - this.descriptor = descriptor; - } - } - - private class VisitorBridge extends VisitorMethod { - private final boolean hasBridgeFlag; - private final List methodRefs = new ArrayList<>(); - - public VisitorBridge(int api, int access, MethodVisitor methodVisitor, JarClassEntry classEntry, JarMethodEntry entry) { - super(api, methodVisitor, classEntry, entry); - hasBridgeFlag = ((access & Opcodes.ACC_BRIDGE) != 0); - } - - @Override - public void visitMethodInsn( - final int opcode, - final String owner, - final String name, - final String descriptor, - final boolean isInterface) { - super.visitMethodInsn(opcode, owner, name, descriptor, isInterface); - methodRefs.add(new MethodRef(owner, name, descriptor)); - } - - @Override - public void visitEnd() { - /* boolean isBridge = hasBridgeFlag; - - if (!isBridge && methodRefs.size() == 1) { - System.out.println("Found suspicious bridge-looking method: " + classEntry.getFullyQualifiedName() + ":" + entry); - } - - if (isBridge) { - for (MethodRef ref : methodRefs) { - JarClassEntry targetClass = jar.getClass(ref.owner, true); - JarMethodEntry targetMethod = new JarMethodEntry(0, ref.name, ref.descriptor, null); - String targetKey = targetMethod.getKey(); - - targetClass.relatedMethods.computeIfAbsent(targetKey, (a) -> new HashSet<>()).add(Pair.of(classEntry, entry.getKey())); - classEntry.relatedMethods.computeIfAbsent(entry.getKey(), (a) -> new HashSet<>()).add(Pair.of(targetClass, targetKey)); - } - } */ - } - } - - private class VisitorMethod extends MethodVisitor { - final JarClassEntry classEntry; - final JarMethodEntry entry; - - public VisitorMethod(int api, MethodVisitor methodVisitor, JarClassEntry classEntry, JarMethodEntry entry) { - super(api, methodVisitor); - this.classEntry = classEntry; - this.entry = entry; - } - } - - public void apply() throws IOException { - // Stage 1: read .JAR class/field/method meta - try (FileInputStream fileStream = new FileInputStream(jar.file)) { - try (JarInputStream jarStream = new JarInputStream(fileStream)) { - java.util.jar.JarEntry entry; - - while ((entry = jarStream.getNextJarEntry()) != null) { - if (!entry.getName().endsWith(".class")) { - continue; - } - - ClassReader reader = new ClassReader(jarStream); - ClassVisitor visitor = new VisitorClass(StitchUtil.ASM_VERSION, null); - reader.accept(visitor, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); - } - } - } - - System.err.println("Read " + this.jar.getAllClasses().size() + " (" + this.jar.getClasses().size() + ") classes."); - - // Stage 2: find subclasses - this.jar.getAllClasses().forEach((c) -> c.populateParents(jar)); - System.err.println("Populated subclass entries."); - - // Stage 3: join identical MethodEntries - if (joinMethodEntries) { - System.err.println("Joining MethodEntries..."); - Set traversedClasses = StitchUtil.newIdentityHashSet(); - - int joinedMethods = 1; - int uniqueMethods = 0; - - Collection checkedMethods = StitchUtil.newIdentityHashSet(); - - for (JarClassEntry entry : jar.getAllClasses()) { - if (traversedClasses.contains(entry)) { - continue; - } - - ClassPropagationTree tree = new ClassPropagationTree(jar, entry); - if (tree.getClasses().size() == 1) { - traversedClasses.add(entry); - continue; - } - - for (JarClassEntry c : tree.getClasses()) { - for (JarMethodEntry m : c.getMethods()) { - if (!checkedMethods.add(m)) { - continue; - } - - // get all matching entries - List mList = m.getMatchingEntries(jar, c); - - if (mList.size() > 1) { - for (int i = 0; i < mList.size(); i++) { - JarClassEntry key = mList.get(i); - JarMethodEntry value = key.getMethod(m.getKey()); - if (value != m) { - key.methods.put(m.getKey(), m); - joinedMethods++; - } - } - } - } - } - - traversedClasses.addAll(tree.getClasses()); - } - - System.err.println("Joined " + joinedMethods + " MethodEntries (" + uniqueMethods + " unique, " + traversedClasses.size() + " classes)."); - } - - System.err.println("Collecting additional information..."); - - // Stage 4: collect additional info - /* try (FileInputStream fileStream = new FileInputStream(jar.file)) { - try (JarInputStream jarStream = new JarInputStream(fileStream)) { - java.util.jar.JarEntry entry; - - while ((entry = jarStream.getNextJarEntry()) != null) { - if (!entry.getName().endsWith(".class")) { - continue; - } - - ClassReader reader = new ClassReader(jarStream); - ClassVisitor visitor = new VisitorClassStageTwo(StitchUtil.ASM_VERSION, null); - reader.accept(visitor, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); - } - } - } */ - - if (remapper != null) { - System.err.println("Remapping..."); - - Map classTree = new HashMap<>(jar.classTree); - jar.classTree.clear(); - - for (Map.Entry entry : classTree.entrySet()) { - entry.getValue().remap(remapper); - jar.classTree.put(entry.getValue().getKey(), entry.getValue()); - } - } - - System.err.println("- Done. -"); - } + public static class Builder { + private final JarReader reader; + + private Builder(JarReader reader) { + this.reader = reader; + } + + public static Builder create(JarRootEntry jar) { + return new Builder(new JarReader(jar)); + } + + public Builder joinMethodEntries(boolean value) { + reader.joinMethodEntries = value; + return this; + } + + public Builder withRemapper(Remapper remapper) { + reader.remapper = remapper; + return this; + } + + public JarReader build() { + return reader; + } + } + + private final JarRootEntry jar; + private boolean joinMethodEntries = true; + private Remapper remapper; + + public JarReader(JarRootEntry jar) { + this.jar = jar; + } + + private class VisitorClass extends ClassVisitor { + private JarClassEntry entry; + + VisitorClass(int api, ClassVisitor classVisitor) { + super(api, classVisitor); + } + + @Override + public void visit(final int version, final int access, final String name, final String signature, + final String superName, final String[] interfaces) { + this.entry = jar.getClass(name, true); + this.entry.populate(access, signature, superName, interfaces); + + super.visit(version, access, name, signature, superName, interfaces); + } + + @Override + public FieldVisitor visitField(final int access, final String name, final String descriptor, + final String signature, final Object value) { + JarFieldEntry field = new JarFieldEntry(access, name, descriptor, signature); + this.entry.fields.put(field.getKey(), field); + + return new VisitorField(api, super.visitField(access, name, descriptor, signature, value), + entry, field); + } + + @Override + public MethodVisitor visitMethod(final int access, final String name, final String descriptor, + final String signature, final String[] exceptions) { + JarMethodEntry method = new JarMethodEntry(access, name, descriptor, signature); + this.entry.methods.put(method.getKey(), method); + + return new VisitorMethod(api, super.visitMethod(access, name, descriptor, signature, exceptions), + entry, method); + } + } + + private class VisitorClassStageTwo extends ClassVisitor { + private JarClassEntry entry; + + VisitorClassStageTwo(int api, ClassVisitor classVisitor) { + super(api, classVisitor); + } + + @Override + public void visit(final int version, final int access, final String name, final String signature, + final String superName, final String[] interfaces) { + this.entry = jar.getClass(name, true); + super.visit(version, access, name, signature, superName, interfaces); + } + + @Override + public MethodVisitor visitMethod(final int access, final String name, final String descriptor, + final String signature, final String[] exceptions) { + JarMethodEntry method = new JarMethodEntry(access, name, descriptor, signature); + this.entry.methods.put(method.getKey(), method); + + if ((access & (Opcodes.ACC_BRIDGE | Opcodes.ACC_SYNTHETIC)) != 0) { + return new VisitorBridge(api, access, super.visitMethod(access, name, descriptor, signature, exceptions), + entry, method); + } else { + return super.visitMethod(access, name, descriptor, signature, exceptions); + } + } + } + + private class VisitorField extends FieldVisitor { + private final JarClassEntry classEntry; + private final JarFieldEntry entry; + + VisitorField(int api, FieldVisitor fieldVisitor, JarClassEntry classEntry, JarFieldEntry entry) { + super(api, fieldVisitor); + this.classEntry = classEntry; + this.entry = entry; + } + } + + private static class MethodRef { + final String owner, name, descriptor; + + MethodRef(String owner, String name, String descriptor) { + this.owner = owner; + this.name = name; + this.descriptor = descriptor; + } + } + + private class VisitorBridge extends VisitorMethod { + private final boolean hasBridgeFlag; + private final List methodRefs = new ArrayList<>(); + + VisitorBridge(int api, int access, MethodVisitor methodVisitor, JarClassEntry classEntry, JarMethodEntry entry) { + super(api, methodVisitor, classEntry, entry); + hasBridgeFlag = ((access & Opcodes.ACC_BRIDGE) != 0); + } + + @Override + public void visitMethodInsn( + final int opcode, + final String owner, + final String name, + final String descriptor, + final boolean isInterface) { + super.visitMethodInsn(opcode, owner, name, descriptor, isInterface); + methodRefs.add(new MethodRef(owner, name, descriptor)); + } + + @Override + public void visitEnd() { + /* boolean isBridge = hasBridgeFlag; + + if (!isBridge && methodRefs.size() == 1) { + System.out.println("Found suspicious bridge-looking method: " + classEntry.getFullyQualifiedName() + ":" + entry); + } + + if (isBridge) { + for (MethodRef ref : methodRefs) { + JarClassEntry targetClass = jar.getClass(ref.owner, true); + JarMethodEntry targetMethod = new JarMethodEntry(0, ref.name, ref.descriptor, null); + String targetKey = targetMethod.getKey(); + + targetClass.relatedMethods.computeIfAbsent(targetKey, (a) -> new HashSet<>()).add(Pair.of(classEntry, entry.getKey())); + classEntry.relatedMethods.computeIfAbsent(entry.getKey(), (a) -> new HashSet<>()).add(Pair.of(targetClass, targetKey)); + } + } */ + } + } + + private class VisitorMethod extends MethodVisitor { + final JarClassEntry classEntry; + final JarMethodEntry entry; + + VisitorMethod(int api, MethodVisitor methodVisitor, JarClassEntry classEntry, JarMethodEntry entry) { + super(api, methodVisitor); + this.classEntry = classEntry; + this.entry = entry; + } + } + + public void apply() throws IOException { + // Stage 1: read .JAR class/field/method meta + try (FileInputStream fileStream = new FileInputStream(jar.file)) { + try (JarInputStream jarStream = new JarInputStream(fileStream)) { + java.util.jar.JarEntry entry; + + while ((entry = jarStream.getNextJarEntry()) != null) { + if (!entry.getName().endsWith(".class")) { + continue; + } + + ClassReader reader = new ClassReader(jarStream); + ClassVisitor visitor = new VisitorClass(StitchUtil.ASM_VERSION, null); + reader.accept(visitor, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); + } + } + } + + System.err.println("Read " + this.jar.getAllClasses().size() + " (" + this.jar.getClasses().size() + ") classes."); + + // Stage 2: find subclasses + this.jar.getAllClasses().forEach((c) -> c.populateParents(jar)); + System.err.println("Populated subclass entries."); + + // Stage 3: join identical MethodEntries + if (joinMethodEntries) { + System.err.println("Joining MethodEntries..."); + Set traversedClasses = StitchUtil.newIdentityHashSet(); + + int joinedMethods = 1; + int uniqueMethods = 0; + + Collection checkedMethods = StitchUtil.newIdentityHashSet(); + + for (JarClassEntry entry : jar.getAllClasses()) { + if (traversedClasses.contains(entry)) { + continue; + } + + ClassPropagationTree tree = new ClassPropagationTree(jar, entry); + + if (tree.getClasses().size() == 1) { + traversedClasses.add(entry); + continue; + } + + for (JarClassEntry c : tree.getClasses()) { + for (JarMethodEntry m : c.getMethods()) { + if (!checkedMethods.add(m)) { + continue; + } + + // get all matching entries + List mList = m.getMatchingEntries(jar, c); + + if (mList.size() > 1) { + for (int i = 0; i < mList.size(); i++) { + JarClassEntry key = mList.get(i); + JarMethodEntry value = key.getMethod(m.getKey()); + + if (value != m) { + key.methods.put(m.getKey(), m); + joinedMethods++; + } + } + } + } + } + + traversedClasses.addAll(tree.getClasses()); + } + + System.err.println("Joined " + joinedMethods + " MethodEntries (" + uniqueMethods + " unique, " + traversedClasses.size() + " classes)."); + } + + System.err.println("Collecting additional information..."); + + // Stage 4: collect additional info + /* try (FileInputStream fileStream = new FileInputStream(jar.file)) { + try (JarInputStream jarStream = new JarInputStream(fileStream)) { + java.util.jar.JarEntry entry; + + while ((entry = jarStream.getNextJarEntry()) != null) { + if (!entry.getName().endsWith(".class")) { + continue; + } + + ClassReader reader = new ClassReader(jarStream); + ClassVisitor visitor = new VisitorClassStageTwo(StitchUtil.ASM_VERSION, null); + reader.accept(visitor, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); + } + } + } */ + + if (remapper != null) { + System.err.println("Remapping..."); + + Map classTree = new HashMap<>(jar.classTree); + jar.classTree.clear(); + + for (Map.Entry entry : classTree.entrySet()) { + entry.getValue().remap(remapper); + jar.classTree.put(entry.getValue().getKey(), entry.getValue()); + } + } + + System.err.println("- Done. -"); + } } diff --git a/src/main/java/net/fabricmc/stitch/representation/JarRootEntry.java b/src/main/java/net/fabricmc/stitch/representation/JarRootEntry.java index 169ecb5..86689e2 100644 --- a/src/main/java/net/fabricmc/stitch/representation/JarRootEntry.java +++ b/src/main/java/net/fabricmc/stitch/representation/JarRootEntry.java @@ -17,67 +17,74 @@ package net.fabricmc.stitch.representation; import java.io.File; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; public class JarRootEntry extends AbstractJarEntry implements ClassStorage { - final Object syncObject = new Object(); - final File file; - final Map classTree; - final List allClasses; - - public JarRootEntry(File file) { - super(file.getName()); - - this.file = file; - this.classTree = new TreeMap<>(Comparator.naturalOrder()); - this.allClasses = new ArrayList<>(); - } - - @Override - public JarClassEntry getClass(String name, boolean create) { - if (name == null) { - return null; - } - - String[] nameSplit = name.split("\\$"); - int i = 0; - - JarClassEntry parent; - JarClassEntry entry = classTree.get(nameSplit[i++]); - if (entry == null && create) { - entry = new JarClassEntry(nameSplit[0], nameSplit[0]); - synchronized (syncObject) { - allClasses.add(entry); - classTree.put(entry.getName(), entry); - } - } - - StringBuilder fullyQualifiedBuilder = new StringBuilder(nameSplit[0]); - - while (i < nameSplit.length && entry != null) { - fullyQualifiedBuilder.append('$'); - fullyQualifiedBuilder.append(nameSplit[i]); - - parent = entry; - entry = entry.getInnerClass(nameSplit[i++]); - - if (entry == null && create) { - entry = new JarClassEntry(nameSplit[i - 1], fullyQualifiedBuilder.toString()); - synchronized (syncObject) { - allClasses.add(entry); - parent.innerClasses.put(entry.getName(), entry); - } - } - } - - return entry; - } - - public Collection getClasses() { - return classTree.values(); - } - - public Collection getAllClasses() { - return Collections.unmodifiableList(allClasses); - } + final Object syncObject = new Object(); + final File file; + final Map classTree; + final List allClasses; + + public JarRootEntry(File file) { + super(file.getName()); + + this.file = file; + this.classTree = new TreeMap<>(Comparator.naturalOrder()); + this.allClasses = new ArrayList<>(); + } + + @Override + public JarClassEntry getClass(String name, boolean create) { + if (name == null) { + return null; + } + + String[] nameSplit = name.split("\\$"); + int i = 0; + + JarClassEntry parent; + JarClassEntry entry = classTree.get(nameSplit[i++]); + + if (entry == null && create) { + entry = new JarClassEntry(nameSplit[0], nameSplit[0]); + synchronized (syncObject) { + allClasses.add(entry); + classTree.put(entry.getName(), entry); + } + } + + StringBuilder fullyQualifiedBuilder = new StringBuilder(nameSplit[0]); + + while (i < nameSplit.length && entry != null) { + fullyQualifiedBuilder.append('$'); + fullyQualifiedBuilder.append(nameSplit[i]); + + parent = entry; + entry = entry.getInnerClass(nameSplit[i++]); + + if (entry == null && create) { + entry = new JarClassEntry(nameSplit[i - 1], fullyQualifiedBuilder.toString()); + synchronized (syncObject) { + allClasses.add(entry); + parent.innerClasses.put(entry.getName(), entry); + } + } + } + + return entry; + } + + public Collection getClasses() { + return classTree.values(); + } + + public Collection getAllClasses() { + return Collections.unmodifiableList(allClasses); + } } diff --git a/src/main/java/net/fabricmc/stitch/util/FieldNameFinder.java b/src/main/java/net/fabricmc/stitch/util/FieldNameFinder.java index 8e3ef6d..df8e4a3 100644 --- a/src/main/java/net/fabricmc/stitch/util/FieldNameFinder.java +++ b/src/main/java/net/fabricmc/stitch/util/FieldNameFinder.java @@ -16,20 +16,6 @@ package net.fabricmc.stitch.util; -import net.fabricmc.mappings.EntryTriple; -import org.objectweb.asm.ClassReader; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.tree.AbstractInsnNode; -import org.objectweb.asm.tree.FieldInsnNode; -import org.objectweb.asm.tree.InsnList; -import org.objectweb.asm.tree.LdcInsnNode; -import org.objectweb.asm.tree.MethodInsnNode; -import org.objectweb.asm.tree.MethodNode; -import org.objectweb.asm.tree.analysis.Analyzer; -import org.objectweb.asm.tree.analysis.Frame; -import org.objectweb.asm.tree.analysis.SourceInterpreter; -import org.objectweb.asm.tree.analysis.SourceValue; - import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; @@ -44,8 +30,22 @@ import java.util.jar.JarEntry; import java.util.jar.JarInputStream; -public class FieldNameFinder { +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.FieldInsnNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.LdcInsnNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.analysis.Analyzer; +import org.objectweb.asm.tree.analysis.Frame; +import org.objectweb.asm.tree.analysis.SourceInterpreter; +import org.objectweb.asm.tree.analysis.SourceValue; +import net.fabricmc.mappings.EntryTriple; + +public class FieldNameFinder { public Map findNames(Iterable classes) throws Exception { Map> methods = new HashMap<>(); Map> enumFields = new HashMap<>(); @@ -68,21 +68,25 @@ public Map findNames(Map> allEnumFields for (Map.Entry> entry : classes.entrySet()) { String owner = entry.getKey(); Set enumFields = allEnumFields.getOrDefault(owner, Collections.emptySet()); + for (MethodNode mn : entry.getValue()) { Frame[] frames = analyzer.analyze(owner, mn); - InsnList instrs = mn.instructions; + for (int i = 1; i < instrs.size(); i++) { AbstractInsnNode instr1 = instrs.get(i - 1); AbstractInsnNode instr2 = instrs.get(i); String s = null; - if (instr2.getOpcode() == Opcodes.PUTSTATIC && ((FieldInsnNode) instr2).owner.equals(owner) - && (instr1 instanceof MethodInsnNode && ((MethodInsnNode) instr1).owner.equals(owner) || enumFields.contains(((FieldInsnNode) instr2).desc + ((FieldInsnNode) instr2).name)) - && (instr1.getOpcode() == Opcodes.INVOKESTATIC || (instr1.getOpcode() == Opcodes.INVOKESPECIAL && "".equals(((MethodInsnNode) instr1).name)))) { - + if (instr2.getOpcode() == Opcodes.PUTSTATIC + && ((FieldInsnNode) instr2).owner.equals(owner) + && (instr1 instanceof MethodInsnNode + && ((MethodInsnNode) instr1).owner.equals(owner) + || enumFields.contains(((FieldInsnNode) instr2).desc + ((FieldInsnNode) instr2).name)) + && (instr1.getOpcode() == Opcodes.INVOKESTATIC || (instr1.getOpcode() == Opcodes.INVOKESPECIAL && "".equals(((MethodInsnNode) instr1).name)))) { for (int j = 0; j < frames[i - 1].getStackSize(); j++) { SourceValue sv = frames[i - 1].getStack(j); + for (AbstractInsnNode ci : sv.insns) { if (ci instanceof LdcInsnNode && ((LdcInsnNode) ci).cst instanceof String) { //if (s == null || !s.equals(((LdcInsnNode) ci).cst)) { @@ -104,14 +108,17 @@ public Map findNames(Map> allEnumFields int separator = s.indexOf('/'); String sFirst = s.substring(0, separator); String sLast; + if (s.contains(".") && s.indexOf('.') > separator) { sLast = s.substring(separator + 1, s.indexOf('.')); } else { sLast = s.substring(separator + 1); } + if (sFirst.endsWith("s")) { sFirst = sFirst.substring(0, sFirst.length() - 1); } + s = sLast + "_" + sFirst; } @@ -163,7 +170,7 @@ public Map findNames(File file) { try { try (FileInputStream fis = new FileInputStream(file); - JarInputStream jis = new JarInputStream(fis)) { + JarInputStream jis = new JarInputStream(fis)) { byte[] buffer = new byte[32768]; JarEntry entry; @@ -174,6 +181,7 @@ public Map findNames(File file) { ByteArrayOutputStream stream = new ByteArrayOutputStream(); int l; + while ((l = jis.read(buffer, 0, buffer.length)) > 0) { stream.write(buffer, 0, l); } diff --git a/src/main/java/net/fabricmc/stitch/util/MatcherUtil.java b/src/main/java/net/fabricmc/stitch/util/MatcherUtil.java index d5865c6..5268853 100644 --- a/src/main/java/net/fabricmc/stitch/util/MatcherUtil.java +++ b/src/main/java/net/fabricmc/stitch/util/MatcherUtil.java @@ -16,73 +16,74 @@ package net.fabricmc.stitch.util; -import net.fabricmc.mappings.EntryTriple; - import java.io.BufferedReader; import java.io.IOException; import java.util.function.BiConsumer; -import java.util.function.UnaryOperator; + +import net.fabricmc.mappings.EntryTriple; public final class MatcherUtil { - private MatcherUtil() { + public static void read(BufferedReader reader, boolean invert, BiConsumer classMappingConsumer, BiConsumer fieldMappingConsumer, BiConsumer methodMappingConsumer) throws IOException { + String line; + String ownerFrom = null, ownerTo = null; + + while ((line = reader.readLine()) != null) { + String[] parts = line.split("\t"); + + if (parts[0].equals("c") && parts.length == 3) { + // class + ownerFrom = parts[1].substring(1, parts[1].length() - 1); + ownerTo = parts[2].substring(1, parts[2].length() - 1); - } + if (invert) { + classMappingConsumer.accept(ownerTo, ownerFrom); + } else { + classMappingConsumer.accept(ownerFrom, ownerTo); + } + } else if (parts[0].equals("") && ownerFrom != null && parts.length >= 2) { + if (parts[1].equals("f") && parts.length == 4) { + String[] fieldFrom = parts[2].split(";;"); + String[] fieldTo = parts[3].split(";;"); - public static void read(BufferedReader reader, boolean invert, BiConsumer classMappingConsumer, BiConsumer fieldMappingConsumer, BiConsumer methodMappingConsumer) throws IOException { - String line; - String ownerFrom = null, ownerTo = null; + if (invert) { + fieldMappingConsumer.accept( + new EntryTriple(ownerTo, fieldTo[0], fieldTo[1]), + new EntryTriple(ownerFrom, fieldFrom[0], fieldFrom[1]) + ); + } else { + fieldMappingConsumer.accept( + new EntryTriple(ownerFrom, fieldFrom[0], fieldFrom[1]), + new EntryTriple(ownerTo, fieldTo[0], fieldTo[1]) + ); + } + } else if (parts[1].equals("m") && parts.length == 4) { + String[] methodFrom = toMethodArray(parts[2]); + String[] methodTo = toMethodArray(parts[3]); - while ((line = reader.readLine()) != null) { - String[] parts = line.split("\t"); + if (invert) { + methodMappingConsumer.accept( + new EntryTriple(ownerTo, methodTo[0], methodTo[1]), + new EntryTriple(ownerFrom, methodFrom[0], methodFrom[1]) + ); + } else { + methodMappingConsumer.accept( + new EntryTriple(ownerFrom, methodFrom[0], methodFrom[1]), + new EntryTriple(ownerTo, methodTo[0], methodTo[1]) + ); + } + } + } + } + } - if (parts[0].equals("c") && parts.length == 3) { - // class - ownerFrom = parts[1].substring(1, parts[1].length() - 1); - ownerTo = parts[2].substring(1, parts[2].length() - 1); - if (invert) { - classMappingConsumer.accept(ownerTo, ownerFrom); - } else { - classMappingConsumer.accept(ownerFrom, ownerTo); - } - } else if (parts[0].equals("") && ownerFrom != null && parts.length >= 2) { - if (parts[1].equals("f") && parts.length == 4) { - String[] fieldFrom = parts[2].split(";;"); - String[] fieldTo = parts[3].split(";;"); - if (invert) { - fieldMappingConsumer.accept( - new EntryTriple(ownerTo, fieldTo[0], fieldTo[1]), - new EntryTriple(ownerFrom, fieldFrom[0], fieldFrom[1]) - ); - } else { - fieldMappingConsumer.accept( - new EntryTriple(ownerFrom, fieldFrom[0], fieldFrom[1]), - new EntryTriple(ownerTo, fieldTo[0], fieldTo[1]) - ); - } - } else if (parts[1].equals("m") && parts.length == 4) { - String[] methodFrom = toMethodArray(parts[2]); - String[] methodTo = toMethodArray(parts[3]); - if (invert) { - methodMappingConsumer.accept( - new EntryTriple(ownerTo, methodTo[0], methodTo[1]), - new EntryTriple(ownerFrom, methodFrom[0], methodFrom[1]) - ); - } else { - methodMappingConsumer.accept( - new EntryTriple(ownerFrom, methodFrom[0], methodFrom[1]), - new EntryTriple(ownerTo, methodTo[0], methodTo[1]) - ); - } - } - } - } - } + private static String[] toMethodArray(String part) { + int parenPos = part.indexOf('('); + return new String[] { + part.substring(0, parenPos), + part.substring(parenPos) + }; + } - private static String[] toMethodArray(String part) { - int parenPos = part.indexOf('('); - return new String[] { - part.substring(0, parenPos), - part.substring(parenPos) - }; - } -} \ No newline at end of file + private MatcherUtil() { + } +} diff --git a/src/main/java/net/fabricmc/stitch/util/NameFinderVisitor.java b/src/main/java/net/fabricmc/stitch/util/NameFinderVisitor.java index 79b62c7..f128ae5 100644 --- a/src/main/java/net/fabricmc/stitch/util/NameFinderVisitor.java +++ b/src/main/java/net/fabricmc/stitch/util/NameFinderVisitor.java @@ -16,18 +16,18 @@ package net.fabricmc.stitch.util; -import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.FieldVisitor; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.tree.MethodNode; - import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.MethodNode; + public class NameFinderVisitor extends ClassVisitor { private String owner; private final Map> allEnumFields; @@ -52,6 +52,7 @@ public FieldVisitor visitField(int access, String name, String descriptor, Strin throw new IllegalArgumentException("Found two enum fields with the same name \"" + name + "\"!"); } } + return super.visitField(access, name, descriptor, signature, value); } diff --git a/src/main/java/net/fabricmc/stitch/util/Pair.java b/src/main/java/net/fabricmc/stitch/util/Pair.java index 1025f67..4d490a1 100644 --- a/src/main/java/net/fabricmc/stitch/util/Pair.java +++ b/src/main/java/net/fabricmc/stitch/util/Pair.java @@ -19,57 +19,57 @@ import java.util.Objects; public final class Pair { - private final K left; - private final V right; + private final K left; + private final V right; - private Pair(K left, V right) { - this.left = left; - this.right = right; - } + private Pair(K left, V right) { + this.left = left; + this.right = right; + } - public static Pair of(K left, V right) { - return new Pair<>(left, right); - } + public static Pair of(K left, V right) { + return new Pair<>(left, right); + } - public K getLeft() { - return left; - } + public K getLeft() { + return left; + } - public V getRight() { - return right; - } + public V getRight() { + return right; + } - @Override - protected Object clone() throws CloneNotSupportedException { - //noinspection unchecked - return new Pair(left, right); - } + @Override + protected Object clone() throws CloneNotSupportedException { + //noinspection unchecked + return new Pair(left, right); + } - @Override - public boolean equals(Object o) { - if (!(o instanceof Pair)) { - return false; - } else { - Pair other = (Pair) o; - return Objects.equals(other.left, left) && Objects.equals(other.right, right); - } - } + @Override + public boolean equals(Object o) { + if (!(o instanceof Pair)) { + return false; + } else { + Pair other = (Pair) o; + return Objects.equals(other.left, left) && Objects.equals(other.right, right); + } + } - @Override - public int hashCode() { - if (left == null && right == null) { - return 0; - } else if (left == null) { - return right.hashCode(); - } else if (right == null) { - return left.hashCode(); - } else { - return left.hashCode() * 19 + right.hashCode(); - } - } + @Override + public int hashCode() { + if (left == null && right == null) { + return 0; + } else if (left == null) { + return right.hashCode(); + } else if (right == null) { + return left.hashCode(); + } else { + return left.hashCode() * 19 + right.hashCode(); + } + } - @Override - public String toString() { - return "Pair(" + left + "," + right + ")"; - } + @Override + public String toString() { + return "Pair(" + left + "," + right + ")"; + } } diff --git a/src/main/java/net/fabricmc/stitch/util/RecordValidator.java b/src/main/java/net/fabricmc/stitch/util/RecordValidator.java index 35fad3d..e8c7ff4 100644 --- a/src/main/java/net/fabricmc/stitch/util/RecordValidator.java +++ b/src/main/java/net/fabricmc/stitch/util/RecordValidator.java @@ -16,15 +16,6 @@ package net.fabricmc.stitch.util; -import org.objectweb.asm.ClassReader; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.tree.AbstractInsnNode; -import org.objectweb.asm.tree.ClassNode; -import org.objectweb.asm.tree.FieldNode; -import org.objectweb.asm.tree.InvokeDynamicInsnNode; -import org.objectweb.asm.tree.MethodNode; -import org.objectweb.asm.tree.RecordComponentNode; - import java.io.File; import java.io.IOException; import java.nio.file.FileVisitResult; @@ -35,6 +26,15 @@ import java.util.LinkedList; import java.util.List; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldNode; +import org.objectweb.asm.tree.InvokeDynamicInsnNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.RecordComponentNode; + public class RecordValidator implements AutoCloseable { private static final String[] REQUIRED_METHOD_SIGNATURES = new String[]{ "toString()Ljava/lang/String;", @@ -45,7 +45,6 @@ public class RecordValidator implements AutoCloseable { private final StitchUtil.FileSystemDelegate inputFs; private final Path inputJar; private final boolean printInfo; - private final List errors = new LinkedList<>(); public RecordValidator(File jarFile, boolean printInfo) throws IOException { @@ -89,6 +88,7 @@ private boolean validateClass(byte[] classBytes) { for (RecordComponentNode component : classNode.recordComponents) { // Ensure that a matching method is present boolean foundMethod = false; + for (MethodNode method : classNode.methods) { if (method.name.equals(component.name) && method.desc.equals("()" +component.descriptor)) { foundMethod = true; @@ -98,6 +98,7 @@ private boolean validateClass(byte[] classBytes) { // Ensure that a matching field is present boolean foundField = false; + for (FieldNode field : classNode.fields) { if (field.name.equals(component.name) && field.desc.equals(component.descriptor)) { foundField = true; @@ -117,6 +118,7 @@ private boolean validateClass(byte[] classBytes) { // Ensure that all of the expected methods are present for (String requiredMethodSignature : REQUIRED_METHOD_SIGNATURES) { boolean foundMethod = false; + for (MethodNode method : classNode.methods) { if ((method.name + method.desc).equals(requiredMethodSignature)) { foundMethod = true; @@ -140,7 +142,6 @@ private boolean validateClass(byte[] classBytes) { // Just print some info out about the record. private void printInfo(ClassNode classNode) { StringBuilder sb = new StringBuilder(); - sb.append("Found record ").append(classNode.name).append(" with components:\n"); for (RecordComponentNode componentNode : classNode.recordComponents) { @@ -174,12 +175,10 @@ private String extractToString(ClassNode classNode) { for (AbstractInsnNode insnNode : methodNode.instructions) { if (insnNode instanceof InvokeDynamicInsnNode) { InvokeDynamicInsnNode invokeDynamic = (InvokeDynamicInsnNode) insnNode; - if ( - !invokeDynamic.name.equals("toString") || - !invokeDynamic.desc.equals(String.format("(L%s;)Ljava/lang/String;", classNode.name)) || - !invokeDynamic.bsm.getName().equals("bootstrap") || - !invokeDynamic.bsm.getOwner().equals("java/lang/runtime/ObjectMethods") - ) { + if (!invokeDynamic.name.equals("toString") + || !invokeDynamic.desc.equals(String.format("(L%s;)Ljava/lang/String;", classNode.name)) + || !invokeDynamic.bsm.getName().equals("bootstrap") + || !invokeDynamic.bsm.getOwner().equals("java/lang/runtime/ObjectMethods")) { // Not what we are looking for continue; } diff --git a/src/main/java/net/fabricmc/stitch/util/SnowmanClassVisitor.java b/src/main/java/net/fabricmc/stitch/util/SnowmanClassVisitor.java index 7e165f3..fba3333 100644 --- a/src/main/java/net/fabricmc/stitch/util/SnowmanClassVisitor.java +++ b/src/main/java/net/fabricmc/stitch/util/SnowmanClassVisitor.java @@ -44,9 +44,11 @@ public void visitLocalVariable( final Label end, final int index) { String newName = name; + if (name != null && name.startsWith("\u2603")) { newName = "lvt" + index; } + super.visitLocalVariable(newName, descriptor, signature, start, end, index); } } diff --git a/src/main/java/net/fabricmc/stitch/util/StitchUtil.java b/src/main/java/net/fabricmc/stitch/util/StitchUtil.java index 3052ea6..69e32ce 100644 --- a/src/main/java/net/fabricmc/stitch/util/StitchUtil.java +++ b/src/main/java/net/fabricmc/stitch/util/StitchUtil.java @@ -16,114 +16,121 @@ package net.fabricmc.stitch.util; -import org.objectweb.asm.Opcodes; - import java.io.File; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; -import java.net.URLEncoder; import java.nio.file.FileSystem; import java.nio.file.FileSystemAlreadyExistsException; import java.nio.file.FileSystems; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; -public final class StitchUtil { +import org.objectweb.asm.Opcodes; - public static int ASM_VERSION = Opcodes.ASM9; - - public static class FileSystemDelegate implements AutoCloseable { - private final FileSystem fileSystem; - private final boolean owner; - - public FileSystemDelegate(FileSystem fileSystem, boolean owner) { - this.fileSystem = fileSystem; - this.owner = owner; - } - - public FileSystem get() { - return fileSystem; - } - - @Override - public void close() throws IOException { - if (owner) { - fileSystem.close(); - } - } - } - - private StitchUtil() { - - } - - private static final Map jfsArgsCreate = new HashMap<>(); - private static final Map jfsArgsEmpty = new HashMap<>(); - - static { - jfsArgsCreate.put("create", "true"); - } - - public static FileSystemDelegate getJarFileSystem(File f, boolean create) throws IOException { - URI jarUri; - try { - jarUri = new URI("jar:file", null, f.toURI().getPath(), ""); - } catch (URISyntaxException e) { - throw new IOException(e); - } - - try { - return new FileSystemDelegate(FileSystems.newFileSystem(jarUri, create ? jfsArgsCreate : jfsArgsEmpty), true); - } catch (FileSystemAlreadyExistsException e) { - return new FileSystemDelegate(FileSystems.getFileSystem(jarUri), false); - } - } - - public static String join(String joiner, Collection c) { - StringBuilder builder = new StringBuilder(); - int i = 0; - for (String s : c) { - if ((i++) > 0) { - builder.append(joiner); - } - - builder.append(s); - } - return builder.toString(); - } - - public static Set newIdentityHashSet() { - return Collections.newSetFromMap(new IdentityHashMap<>()); - } - - public static List mergePreserveOrder(List first, List second) { - List out = new ArrayList<>(); - int i = 0; - int j = 0; - - while (i < first.size() || j < second.size()) { - while (i < first.size() && j < second.size() - && first.get(i).equals(second.get(j))) { - out.add(first.get(i)); - i++; - j++; - } - - while (i < first.size() && !second.contains(first.get(i))) { - out.add(first.get(i)); - i++; - } - - while (j < second.size() && !first.contains(second.get(j))) { - out.add(second.get(j)); - j++; - } - } - - return out; - } - - public static long getTime() { - return new Date().getTime(); - } +public final class StitchUtil { + private static final Map jfsArgsCreate = new HashMap<>(); + private static final Map jfsArgsEmpty = new HashMap<>(); + public static final int ASM_VERSION = Opcodes.ASM9; + + static { + jfsArgsCreate.put("create", "true"); + } + + public static class FileSystemDelegate implements AutoCloseable { + private final FileSystem fileSystem; + private final boolean owner; + + public FileSystemDelegate(FileSystem fileSystem, boolean owner) { + this.fileSystem = fileSystem; + this.owner = owner; + } + + public FileSystem get() { + return fileSystem; + } + + @Override + public void close() throws IOException { + if (owner) { + fileSystem.close(); + } + } + } + + public static FileSystemDelegate getJarFileSystem(File f, boolean create) throws IOException { + URI jarUri; + + try { + jarUri = new URI("jar:file", null, f.toURI().getPath(), ""); + } catch (URISyntaxException e) { + throw new IOException(e); + } + + try { + return new FileSystemDelegate(FileSystems.newFileSystem(jarUri, create ? jfsArgsCreate : jfsArgsEmpty), true); + } catch (FileSystemAlreadyExistsException e) { + return new FileSystemDelegate(FileSystems.getFileSystem(jarUri), false); + } + } + + public static String join(String joiner, Collection c) { + StringBuilder builder = new StringBuilder(); + int i = 0; + + for (String s : c) { + if ((i++) > 0) { + builder.append(joiner); + } + + builder.append(s); + } + + return builder.toString(); + } + + public static Set newIdentityHashSet() { + return Collections.newSetFromMap(new IdentityHashMap<>()); + } + + public static List mergePreserveOrder(List first, List second) { + List out = new ArrayList<>(); + int i = 0; + int j = 0; + + while (i < first.size() || j < second.size()) { + while (i < first.size() && j < second.size() + && first.get(i).equals(second.get(j))) { + out.add(first.get(i)); + i++; + j++; + } + + while (i < first.size() && !second.contains(first.get(i))) { + out.add(first.get(i)); + i++; + } + + while (j < second.size() && !first.contains(second.get(j))) { + out.add(second.get(j)); + j++; + } + } + + return out; + } + + public static long getTime() { + return new Date().getTime(); + } + + private StitchUtil() { + } } diff --git a/src/main/java/net/fabricmc/stitch/util/SyntheticParameterClassVisitor.java b/src/main/java/net/fabricmc/stitch/util/SyntheticParameterClassVisitor.java index 96934a5..2c5c3fd 100644 --- a/src/main/java/net/fabricmc/stitch/util/SyntheticParameterClassVisitor.java +++ b/src/main/java/net/fabricmc/stitch/util/SyntheticParameterClassVisitor.java @@ -25,81 +25,87 @@ * ProGuard has a bug where parameter annotations are applied incorrectly in the presence of * synthetic arguments. This causes javac to balk when trying to load affected classes. * - * We use several heuristics to guess what the synthetic arguments may be for a particular + *

We use several heuristics to guess what the synthetic arguments may be for a particular * constructor. We then check if the constructor matches our guess, and if so, offset all - * parameter annotations. + * parameter annotations.

*/ public class SyntheticParameterClassVisitor extends ClassVisitor { - private class SyntheticMethodVisitor extends MethodVisitor { - private final int offset; - - SyntheticMethodVisitor(int api, int offset, MethodVisitor methodVisitor) { - super(api, methodVisitor); - this.offset = offset; - } - - @Override - public AnnotationVisitor visitParameterAnnotation(int parameter, String descriptor, boolean visible) { - return super.visitParameterAnnotation(parameter - offset, descriptor, visible); - } - - @Override - public void visitAnnotableParameterCount(int parameterCount, boolean visible) { - super.visitAnnotableParameterCount(parameterCount - offset, visible); - } - } - - private String className; - private int synthetic; - private String syntheticArgs; - private boolean backoff = false; - - public SyntheticParameterClassVisitor(int api, ClassVisitor cv) { - super(api, cv); - } - - @Override - public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { - super.visit(version, access, name, signature, superName, interfaces); - - this.className = name; - - // Enums will always have a string name and then the ordinal - if ((access & Opcodes.ACC_ENUM) != 0) { - synthetic = 2; - syntheticArgs = "(Ljava/lang/String;I"; - } - - if (version >= 55) { - // Backoff on java 11 or newer due to nest mates being used. - backoff = true; - } - } - - @Override - public void visitInnerClass(String name, String outerName, String innerName, int access) { - super.visitInnerClass(name, outerName, innerName, access); - - // If we're a non-static, non-anonymous inner class then we can assume the first argument - // is the parent class. - // See https://docs.oracle.com/javase/specs/jls/se11/html/jls-8.html#jls-8.8.1 - if (synthetic == 0 && name.equals(this.className) && innerName != null && outerName != null && (access & Opcodes.ACC_STATIC) == 0) { - this.synthetic = 1; - this.syntheticArgs = "(L" + outerName + ";"; - } - } - - @Override - public MethodVisitor visitMethod( - final int access, - final String name, - final String descriptor, - final String signature, - final String[] exceptions) { - MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions); - - return mv != null && synthetic != 0 && name.equals("") && descriptor.startsWith(syntheticArgs) && !backoff - ? new SyntheticMethodVisitor(api, synthetic, mv) - : mv; - } + private class SyntheticMethodVisitor extends MethodVisitor { + private final int offset; + + SyntheticMethodVisitor(int api, int offset, MethodVisitor methodVisitor) { + super(api, methodVisitor); + this.offset = offset; + } + + @Override + public AnnotationVisitor visitParameterAnnotation(int parameter, String descriptor, boolean visible) { + return super.visitParameterAnnotation(parameter - offset, descriptor, visible); + } + + @Override + public void visitAnnotableParameterCount(int parameterCount, boolean visible) { + super.visitAnnotableParameterCount(parameterCount - offset, visible); + } + } + + private String className; + private int synthetic; + private String syntheticArgs; + private boolean backoff = false; + + public SyntheticParameterClassVisitor(int api, ClassVisitor cv) { + super(api, cv); + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + super.visit(version, access, name, signature, superName, interfaces); + + this.className = name; + + // Enums will always have a string name and then the ordinal + if ((access & Opcodes.ACC_ENUM) != 0) { + synthetic = 2; + syntheticArgs = "(Ljava/lang/String;I"; + } + + if (version >= 55) { + // Backoff on java 11 or newer due to nest mates being used. + backoff = true; + } + } + + @Override + public void visitInnerClass(String name, String outerName, String innerName, int access) { + super.visitInnerClass(name, outerName, innerName, access); + + // If we're a non-static, non-anonymous inner class then we can assume the first argument + // is the parent class. + // See https://docs.oracle.com/javase/specs/jls/se11/html/jls-8.html#jls-8.8.1 + if (synthetic == 0 && name.equals(this.className) && innerName != null && outerName != null && (access & Opcodes.ACC_STATIC) == 0) { + this.synthetic = 1; + this.syntheticArgs = "(L" + outerName + ";"; + } + } + + @Override + public MethodVisitor visitMethod( + final int access, + final String name, + final String descriptor, + final String signature, + final String[] exceptions) { + MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions); + + if (mv != null + && synthetic != 0 + && name.equals("") + && descriptor.startsWith(syntheticArgs) + && !backoff) { + return new SyntheticMethodVisitor(api, synthetic, mv); + } + + return mv; + } } diff --git a/src/test/java/net/fabricmc/stitch/tinyv1/Commands.java b/src/test/java/net/fabricmc/stitch/tinyv1/Commands.java index 7464e85..0fde5e5 100644 --- a/src/test/java/net/fabricmc/stitch/tinyv1/Commands.java +++ b/src/test/java/net/fabricmc/stitch/tinyv1/Commands.java @@ -27,9 +27,9 @@ public class Commands { @Disabled public void testOrdering() throws Exception { String[] args = { - "local\\unordered-merged-mappings.tiny", - "local\\merged-mappings.tiny", - "official", "intermediary", "named" + "local\\unordered-merged-mappings.tiny", + "local\\merged-mappings.tiny", + "official", "intermediary", "named" }; new CommandReorderTiny().run(args); @@ -39,9 +39,9 @@ public void testOrdering() throws Exception { @Disabled public void testProposing() throws Exception { String[] args = { - "local\\19w37a-merged.jar", - "local\\merged-mappings.tiny", - "local\\merged-mappings-proposed.tiny" + "local\\19w37a-merged.jar", + "local\\merged-mappings.tiny", + "local\\merged-mappings-proposed.tiny" }; new CommandProposeFieldNames().run(args); diff --git a/src/test/java/net/fabricmc/stitch/tinyv2/Commands.java b/src/test/java/net/fabricmc/stitch/tinyv2/Commands.java index 8d6a55e..9e4a445 100644 --- a/src/test/java/net/fabricmc/stitch/tinyv2/Commands.java +++ b/src/test/java/net/fabricmc/stitch/tinyv2/Commands.java @@ -34,15 +34,11 @@ public static void reorder(String toInvert, String outputTo, String... newOrder) new CommandReorderTinyV2().run(args.toArray(new String[0])); } - public static void merge(String mappingA, String mappingB, String mergedLocation) throws Exception { new CommandMergeTinyV2().run(new String[] {mappingA, mappingB, mergedLocation}); } - public static void proposeFieldNames(String mergedJar, String mergedTinyFile, String newTinyFile) throws Exception { new CommandProposeV2FieldNames().run(new String[] {mergedJar, mergedTinyFile, newTinyFile}); } - - } diff --git a/src/test/java/net/fabricmc/stitch/tinyv2/Snapshot37a.java b/src/test/java/net/fabricmc/stitch/tinyv2/Snapshot37a.java index 1e3b4d3..52934ef 100644 --- a/src/test/java/net/fabricmc/stitch/tinyv2/Snapshot37a.java +++ b/src/test/java/net/fabricmc/stitch/tinyv2/Snapshot37a.java @@ -24,40 +24,37 @@ import org.junit.jupiter.api.Test; public class Snapshot37a { - private static final String DIR = new File(Snapshot37a.class.getClassLoader().getResource("snapshot-37a").getPath()).getAbsolutePath() + "/"; -// @Test public void testMerge() throws Exception { Commands.merge(DIR + "intermediate-mappings-inverted-stitch.tinyv2", - DIR + "yarn-mappings-stitch.tinyv2", - DIR + "merged-unordered.tinyv2" - ); + DIR + "yarn-mappings-stitch.tinyv2", + DIR + "merged-unordered.tinyv2"); } @Test public void testReorder2() throws Exception { Commands.reorder(DIR + "intermediate-mappings-stitch.tinyv2", - DIR + "intermediate-mappings-inverted-stitch.tinyv2", - "intermediary", "official" - ); + DIR + "intermediate-mappings-inverted-stitch.tinyv2", + "intermediary", "official"); } @Test public void testReorder3() throws Exception { String path = DIR + "merged-unordered.tinyv2"; - if(!Files.exists(Paths.get(path))) testMerge(); + + if (!Files.exists(Paths.get(path))) testMerge(); + Commands.reorder(path, - DIR + "merged.tinyv2", - "official", "intermediary", "named" - ); + DIR + "merged.tinyv2", + "official", "intermediary", "named"); } - /** You need the 19w37a-merged.jar file under local/ */ + /** You need the 19w37a-merged.jar file under 'local/'. */ @Test @Disabled public void testFieldNameProposal() throws Exception { Commands.proposeFieldNames("local/19w37a-merged.jar", - DIR + "merged.tinyv2", DIR + "merged-proposed.tinyv2"); + DIR + "merged.tinyv2", DIR + "merged-proposed.tinyv2"); } } diff --git a/src/test/java/net/fabricmc/stitch/tinyv2/Stable1_14_4.java b/src/test/java/net/fabricmc/stitch/tinyv2/Stable1_14_4.java index 63056af..db345fd 100644 --- a/src/test/java/net/fabricmc/stitch/tinyv2/Stable1_14_4.java +++ b/src/test/java/net/fabricmc/stitch/tinyv2/Stable1_14_4.java @@ -40,9 +40,8 @@ public class Stable1_14_4 { @Test public void testReorder2() throws Exception { Commands.reorder(DIR + "intermediary-mappings.tinyv2", - DIR + "intermediary-mappings-inverted.tinyv2", - "intermediary", "official" - ); + DIR + "intermediary-mappings-inverted.tinyv2", + "intermediary", "official"); } @Test @@ -50,61 +49,52 @@ public void testReorder2() throws Exception { public void testMerge() throws Exception { String target = DIR + "merged-unordered.tinyv2"; Commands.merge(DIR + "intermediary-mappings-inverted.tinyv2", - DIR + "yarn-mappings.tinyv2", - target - ); + DIR + "yarn-mappings.tinyv2", + target); + try (BufferedReader reader = Files.newBufferedReader(Paths.get(target))) { TinyTree mappings = TinyMappingFactory.load(reader); - ParameterDef blockInitParam = findMethodParameterMapping("intermediary", "net/minecraft/class_2248", - "", "(Lnet/minecraft/class_2248$class_2251;)V", 1, mappings); + "", "(Lnet/minecraft/class_2248$class_2251;)V", 1, mappings); Assertions.assertEquals("settings", blockInitParam.getName("named")); - } - } private ClassDef findClassMapping(String column, String key, TinyTree mappings) { return find(mappings.getClasses(), c -> c.getName(column).equals(key)) - .orElseThrow(() -> new AssertionError("Could not find key " + key + " in namespace " + column)); + .orElseThrow(() -> new AssertionError("Could not find key " + key + " in namespace " + column)); } private MethodDef findMethodMapping(String column, String className, String methodName, String descriptor, TinyTree mappings) { return find(findClassMapping(column, className, mappings).getMethods(), - m -> m.getName(column).equals(methodName) && m.getDescriptor(column).equals(descriptor) - - ).orElseThrow(() -> new AssertionError("Could not find key " + className + " " + descriptor + " " + methodName + " in namespace " + column)); + m -> m.getName(column).equals(methodName) && m.getDescriptor(column).equals(descriptor)) + .orElseThrow(() -> new AssertionError("Could not find key " + className + " " + descriptor + " " + methodName + " in namespace " + column)); } - - private ParameterDef findMethodParameterMapping(String column, String className, String methodName, String descriptor, - int lvIndex, TinyTree mappings) { + private ParameterDef findMethodParameterMapping(String column, String className, String methodName, String descriptor, int lvIndex, TinyTree mappings) { MethodDef method = findMethodMapping(column, className, methodName, descriptor, mappings); - return find(method.getParameters(), - p -> p.getLocalVariableIndex() == lvIndex) - .orElseThrow(() -> new AssertionError("Could not find key" + className + " " + descriptor + " " + methodName + " " + lvIndex + " in namespace " + column)); + return find(method.getParameters(), p -> p.getLocalVariableIndex() == lvIndex) + .orElseThrow(() -> new AssertionError("Could not find key" + className + " " + descriptor + " " + methodName + " " + lvIndex + " in namespace " + column)); } private Optional find(Collection list, Predicate predicate) { return list.stream().filter(predicate).findFirst(); } - @Test @Disabled public void testReorder3() throws Exception { Commands.reorder(DIR + "merged-unordered.tinyv2", - DIR + "merged.tinyv2", - "official", "intermediary", "named" - ); + DIR + "merged.tinyv2", + "official", "intermediary", "named"); } @Test @Disabled public void testFieldNameProposal() throws Exception { Commands.proposeFieldNames("local/1.14.4-merged.jar", - DIR + "merged.tinyv2", DIR + "merged-proposed.tinyv2"); + DIR + "merged.tinyv2", DIR + "merged-proposed.tinyv2"); } // Requirements: diff --git a/src/test/java/net/fabricmc/stitch/tinyv2/TestTinyV2ReadAndWrite.java b/src/test/java/net/fabricmc/stitch/tinyv2/TestTinyV2ReadAndWrite.java index dbd74fb..e0ded30 100644 --- a/src/test/java/net/fabricmc/stitch/tinyv2/TestTinyV2ReadAndWrite.java +++ b/src/test/java/net/fabricmc/stitch/tinyv2/TestTinyV2ReadAndWrite.java @@ -37,14 +37,13 @@ private void tryToReadAndWrite(String path) throws IOException { TinyFile tinyFile = TinyV2Reader.read(intMappings); Path tempLocation = Paths.get(path + ".temp"); -// tempLocation.toFile().deleteOnExit(); + // tempLocation.toFile().deleteOnExit(); TinyV2Writer.write(tinyFile, tempLocation); String originalIntMappings = new String(Files.readAllBytes(intMappings)); String writtenIntMappings = new String(Files.readAllBytes(tempLocation)); // Ensure the file has not changed - Assertions.assertEquals(originalIntMappings.replace("\r\n","\n"), writtenIntMappings.replace("\r\n","\n")); - + Assertions.assertEquals(originalIntMappings.replace("\r\n", "\n"), writtenIntMappings.replace("\r\n", "\n")); } @Test diff --git a/src/test/java/net/fabricmc/stitch/tinyv2/TestToString.java b/src/test/java/net/fabricmc/stitch/tinyv2/TestToString.java index bb5065f..dd7380c 100644 --- a/src/test/java/net/fabricmc/stitch/tinyv2/TestToString.java +++ b/src/test/java/net/fabricmc/stitch/tinyv2/TestToString.java @@ -27,20 +27,17 @@ import net.fabricmc.stitch.commands.tinyv2.TinyMethod; public class TestToString { - @Test public void testTinyClassToString() { TinyClass tinyClass = new TinyClass( - Arrays.asList("name1", "name2", "name3"), - Collections.singletonList(new TinyMethod("", - Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList())), - Collections.singletonList(new TinyField("", Collections.emptyList(), Collections.emptyList())), - Collections.singletonList("Asdf") - ); + Arrays.asList("name1", "name2", "name3"), + Collections.singletonList(new TinyMethod("", + Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList())), + Collections.singletonList(new TinyField("", Collections.emptyList(), Collections.emptyList())), + Collections.singletonList("Asdf")); String expected = "TinyClass(names = [name1, name2, name3], 1 methods, 1 fields, 1 comments)"; Assertions.assertEquals(expected, tinyClass.toString()); } - }