├── .gitattributes ├── .gitignore ├── .idea ├── .gitignore ├── .name ├── compiler.xml ├── libraries-with-intellij-classes.xml ├── misc.xml └── vcs.xml ├── LICENSE ├── README.md ├── build.gradle ├── custom_mappings └── mappings_152_converted_yarn_intermediates.zip ├── extras └── added_classes_and_resources_go_here.txt ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── install.bat ├── libs ├── paulscode.zip └── tiny-remapper-0.8.6+local-fat.jar ├── mvn ├── btw │ └── community │ │ └── mappings │ │ └── 1.0.3 │ │ ├── mappings-1.0.3.jar │ │ └── mappings-1.0.3.pom └── net │ └── devtech │ └── grossfabrichacks │ ├── 4.5 │ ├── grossfabrichacks-4.5.jar │ └── grossfabrichacks-4.5.pom │ ├── 6.1-btw │ ├── grossfabrichacks-6.1-btw.jar │ └── grossfabrichacks-6.1-btw.pom │ └── 6.2-btw │ ├── grossfabrichacks-6.2-btw.jar │ └── grossfabrichacks-6.2-btw.pom ├── settings.gradle └── src └── main ├── java ├── btw │ └── community │ │ └── fabric │ │ ├── BTWFabricMod.java │ │ └── mixin │ │ ├── MinecraftMixin.java │ │ ├── MinecraftServerMixin.java │ │ └── NetClientHandlerMixin.java └── net │ └── superblaubeere27 │ └── asmdelta │ ├── ASMDelta.java │ ├── ASMDeltaPatch.java │ ├── ASMDeltaTransformer.java │ ├── Main.java │ ├── difference │ ├── AbstractDifference.java │ ├── VerificationException.java │ ├── clazz │ │ ├── AddClassDifference.java │ │ ├── ClassAccessDifference.java │ │ ├── ClassMakePublicDifference.java │ │ ├── ClassMetadataDifference.java │ │ ├── ClassVersionDifference.java │ │ └── RemoveClassDifference.java │ ├── fields │ │ ├── AddFieldDifference.java │ │ ├── FieldAccessDifference.java │ │ ├── FieldDescriptionDifference.java │ │ ├── FieldSignatureDifference.java │ │ ├── FieldValueDifference.java │ │ └── RemoveFieldDifference.java │ └── methods │ │ ├── AddMethodDifference.java │ │ ├── MethodAccessDifference.java │ │ ├── MethodAnnotationDefaultDifference.java │ │ ├── MethodExceptionDifference.java │ │ ├── MethodInstructionDifference.java │ │ ├── MethodLocalVariableDifference.java │ │ ├── MethodMaxsDifference.java │ │ ├── MethodSignatureDifference.java │ │ └── RemoveMethodDifference.java │ └── utils │ ├── Hex.java │ ├── InstructionComparator.java │ ├── ScheduledRunnable.java │ ├── Scheduler.java │ ├── Utils.java │ └── typeadapter │ ├── AbstractDifferenceSerializer.java │ ├── ClassNodeSerializer.java │ ├── MethodLocalVariableDifferenceSerializer.java │ └── MethodNodeSerializer.java └── resources ├── cursedbtw.mixins.json └── fabric.mod.json /.gitattributes: -------------------------------------------------------------------------------- 1 | # 2 | # https://help.github.com/articles/dealing-with-line-endings/ 3 | # 4 | # These are explicitly windows files and should use crlf 5 | *.bat text eol=crlf 6 | 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore Gradle project-specific cache directory 2 | .gradle 3 | 4 | # Ignore Gradle build output directories 5 | **/build/ 6 | !src/**/build/ 7 | 8 | # Screws up the MC dev plugin for IntelliJ.. 9 | # src/btw/** 10 | 11 | # Ignore local Minecraft folder 12 | .minecraft 13 | 14 | # IntelliJ 15 | *.iml 16 | .idea/workspace.xml 17 | .idea/tasks.xml 18 | .idea/gradle.xml 19 | .idea/assetWizardSettings.xml 20 | .idea/dictionaries 21 | .idea/libraries 22 | .idea/jarRepositories.xml 23 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | BTW Fabric -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/libraries-with-intellij-classes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 64 | 65 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Arminias 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cursed-BTW 2 | 3 | This repository is based on the [BTW Addon Example Mod](https://github.com/BTW-Community/BTW-gradle-fabric-example) 4 | repository. It makes the Better Than Wolves mod usable with just a Fabric installation, without Jar modding. 5 | 6 | ## License 7 | This project incorporates: 8 | * A modified version of [Fabric Loom](https://github.com/FabricMC/fabric-loom) (MIT) 9 | * A precompiled version of [Tiny Remapper](https://github.com/FabricMC/tiny-remapper) (LGPL-3.0) 10 | * A modified version of [asm-delta-agent](https://github.com/superblaubeere27/asm-delta-agent) (MIT) 11 | * A modified version of [asm-delta](https://github.com/superblaubeere27/asm-delta) (MIT) 12 | * A modified version of [GrossFabricHacks](https://github.com/Devan-Kerman/GrossFabricHacks) (MPL-2.0) 13 | * An adaption of [NoVerify](https://github.com/char/noverify-hackery) (GPL) 14 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'maven-publish' 3 | id 'fabric-loom' version "1.5-SNAPSHOT" 4 | id 'application' 5 | id("com.github.johnrengelman.shadow") version "8.1.1" 6 | } 7 | 8 | archivesBaseName = project.archives_base_name 9 | version = project.mod_version 10 | group = project.maven_group 11 | 12 | repositories { 13 | maven { 14 | name = 'legacy-fabric' 15 | url = 'https://maven.legacyfabric.net' 16 | } 17 | maven { 18 | url 'https://jitpack.io' 19 | } 20 | maven { 21 | name 'HalfOf2' 22 | url 'https://storage.googleapis.com/devan-maven/' 23 | } 24 | mavenLocal { 25 | url = 'mvn' 26 | } 27 | /*maven { 28 | url = 'https://raw.githubusercontent.com/Devan-Kerman/Devan-Repo/master/' 29 | }*/ 30 | } 31 | 32 | loom { 33 | setIntermediaryUrl('https://maven.legacyfabric.net/net/legacyfabric/intermediary/%1$s/intermediary-%1$s-v2.jar') 34 | sourceCompatibility = JavaVersion.VERSION_17 35 | targetCompatibility = JavaVersion.VERSION_17 36 | } 37 | 38 | 39 | sourceSets { 40 | btw { 41 | java { 42 | srcDirs = ['src/btw/java'] 43 | } 44 | resources { 45 | srcDirs = ['src/btw/resources'] 46 | } 47 | } 48 | } 49 | 50 | def lwjglVersion = System.getProperty("os.name").toLowerCase().contains("mac") ? "2.9.1" : "2.9.0" 51 | 52 | dependencies { 53 | minecraft("com.mojang:minecraft:${project.minecraft_version}") { 54 | exclude group: 'com.google' 55 | } 56 | mappings "btw.community:mappings:1.0.3" 57 | 58 | modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" 59 | implementation 'net.fabricmc:fabric-loom:1.5-SNAPSHOT' 60 | 61 | implementation 'org.apache.logging.log4j:log4j-core:2.17.1' 62 | implementation 'org.apache.logging.log4j:log4j-api:2.17.1' 63 | implementation 'org.apache.commons:commons-lang3:3.12.0' 64 | implementation 'commons-io:commons-io:2.4' 65 | 66 | implementation "org.lwjgl.lwjgl:lwjgl_util:${lwjglVersion}" 67 | implementation "org.lwjgl.lwjgl:lwjgl:${lwjglVersion}" 68 | implementation "org.lwjgl.lwjgl:lwjgl-platform:${lwjglVersion}" 69 | 70 | implementation 'com.google.guava:guava:14.0.1' 71 | 72 | // This is what MC 1.6 uses 73 | shadow(include(implementation('com.google.code.gson:gson:2.8.9'))) 74 | 75 | implementation("net.sourceforge.argo:argo:3.12") 76 | 77 | implementation fileTree(dir: "libs", include: "**.jar") 78 | modImplementation(group: 'net.devtech', name: 'grossfabrichacks', version: '6.2-btw') { 79 | exclude group: 'net.fabricmc' 80 | } 81 | 82 | implementation('it.unimi.dsi:fastutil:8.5.12') 83 | include 'net.devtech:grossfabrichacks:6.2-btw' 84 | include 'org.apache.logging.log4j:log4j-core:2.19.0' 85 | include 'org.apache.logging.log4j:log4j-api:2.19.0' 86 | 87 | implementation("net.auoeke:reflect:6.+") 88 | implementation("net.auoeke:unsafe:latest.release") 89 | 90 | implementation fileTree(dir: "libs", include: "**.zip") 91 | compileOnly fileTree(dir: "$projectDir/BTW_dev", include: "*.zip") 92 | compileOnly fileTree(dir: "$buildDir/BTW_dev", include: "**.jar") 93 | 94 | runtimeOnly fileTree(dir: "$buildDir/dev_run", include: "dev.jar") 95 | } 96 | 97 | configurations { 98 | btwCompileClasspath.extendsFrom implementation, modImplementation 99 | } 100 | 101 | shadowJar { 102 | from jar 103 | configurations = [project.configurations.shadow] 104 | } 105 | 106 | configurations.all { 107 | resolutionStrategy { 108 | dependencySubstitution { 109 | substitute module('org.lwjgl.lwjgl:lwjgl_util:2.9.1-nightly-20130708-debug3') with { 110 | module("org.lwjgl.lwjgl:lwjgl_util:${lwjglVersion}") 111 | } 112 | substitute module('org.lwjgl.lwjgl:lwjgl:2.9.1-nightly-20130708-debug3') with { 113 | module("org.lwjgl.lwjgl:lwjgl:${lwjglVersion}") 114 | } 115 | } 116 | force "org.lwjgl.lwjgl:lwjgl-platform:${lwjglVersion}" 117 | } 118 | } 119 | 120 | processResources { 121 | inputs.property "version", project.version 122 | filesMatching("fabric.mod.json") { 123 | expand "version": project.version 124 | } 125 | } 126 | 127 | java { 128 | withSourcesJar() 129 | } 130 | 131 | compileJava { 132 | dependsOn('btwJar') 133 | } 134 | 135 | task devPackMod(type:Copy) { 136 | dependsOn('jar') 137 | from sourceSets.main.output.classesDirs 138 | into file("$buildDir/classes/java/btw") 139 | } 140 | 141 | task devPackBTW(type:Copy) { 142 | dependsOn('devPackMod') 143 | dependsOn('btwJar') 144 | } 145 | 146 | task devPackRun(type:Jar) { 147 | dependsOn('devPackBTW') 148 | from fileTree("$buildDir/classes/java/btw") 149 | from fileTree("$projectDir/BTW_dev/") 150 | from sourceSets.btw.output.resourcesDir 151 | destinationDirectory = file("$buildDir/dev_run") 152 | archiveFileName = "dev.jar" 153 | } 154 | 155 | jar { 156 | from sourceSets.main.output.resourcesDir 157 | from sourceSets.main.output.classesDirs 158 | from fileTree("$projectDir/extras/") 159 | from("LICENSE") { 160 | rename { "${it}_${project.archivesBaseName}"} 161 | } 162 | // Set main 163 | manifest { 164 | attributes( 165 | "Main-Class": "net.superblaubeere27.asmdelta.Main", 166 | ) 167 | } 168 | } 169 | 170 | task btwJar(type:Jar) { 171 | from fileTree("$projectDir/BTW_dev/") 172 | from sourceSets.btw.output.resourcesDir 173 | from sourceSets.btw.output.classesDirs 174 | destinationDirectory = file("$buildDir/BTW_dev") 175 | archiveFileName = "BTW_dev.jar" 176 | } 177 | 178 | remapJar { 179 | dependsOn shadowJar 180 | input.set shadowJar.archiveFile.get() 181 | destinationDirectory = file("$rootDir/release") 182 | } 183 | 184 | runClient { 185 | dependsOn('devPackRun') 186 | } 187 | 188 | // run net.superblaubeere27.amdelta.Main 189 | application { 190 | mainClass = "net.superblaubeere27.asmdelta.Main" 191 | } 192 | 193 | run { 194 | applicationDefaultJvmArgs += "-Xss16M" 195 | args = ["--jar1", "vanilla-server-release.jar", "--jar2", "btw-server-release.jar", "-o", "./output-server-release.patch"] 196 | //args = ["--jar1", "vanilla-client-release.jar", "--jar2", "btw-client-release.jar", "-o", "./output-client-release.patch"] 197 | } 198 | 199 | project.afterEvaluate { 200 | devPackRun.dependsOn -= downloadAssets 201 | } 202 | 203 | runServer { 204 | dependsOn('devPackRun') 205 | } 206 | 207 | clean.doFirst { 208 | delete "$buildDir/dev_run" 209 | delete "$buildDir/BTW_dev" 210 | } 211 | -------------------------------------------------------------------------------- /custom_mappings/mappings_152_converted_yarn_intermediates.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BTW-Community/Cursed-BTW/d8b7d785d1937f547ff0c35d9d7bdb7dff994478/custom_mappings/mappings_152_converted_yarn_intermediates.zip -------------------------------------------------------------------------------- /extras/added_classes_and_resources_go_here.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BTW-Community/Cursed-BTW/d8b7d785d1937f547ff0c35d9d7bdb7dff994478/extras/added_classes_and_resources_go_here.txt -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project name (required) 2 | name=BTW Fabric 3 | 4 | minecraft_version = 1.6.4 5 | yarn_mappings = 1.6.4+build.420 6 | loader_version = 0.14.19 7 | 8 | mod_version = 1.0.0 9 | maven_group = net.fabricmc 10 | archives_base_name = btw-fabric 11 | 12 | org.gradle.jvmargs=-Xmx4g 13 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BTW-Community/Cursed-BTW/d8b7d785d1937f547ff0c35d9d7bdb7dff994478/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 87 | APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit 88 | 89 | # Use the maximum available, or set MAX_FD != -1 to use that value. 90 | MAX_FD=maximum 91 | 92 | warn () { 93 | echo "$*" 94 | } >&2 95 | 96 | die () { 97 | echo 98 | echo "$*" 99 | echo 100 | exit 1 101 | } >&2 102 | 103 | # OS specific support (must be 'true' or 'false'). 104 | cygwin=false 105 | msys=false 106 | darwin=false 107 | nonstop=false 108 | case "$( uname )" in #( 109 | CYGWIN* ) cygwin=true ;; #( 110 | Darwin* ) darwin=true ;; #( 111 | MSYS* | MINGW* ) msys=true ;; #( 112 | NONSTOP* ) nonstop=true ;; 113 | esac 114 | 115 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 116 | 117 | 118 | # Determine the Java command to use to start the JVM. 119 | if [ -n "$JAVA_HOME" ] ; then 120 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 121 | # IBM's JDK on AIX uses strange locations for the executables 122 | JAVACMD=$JAVA_HOME/jre/sh/java 123 | else 124 | JAVACMD=$JAVA_HOME/bin/java 125 | fi 126 | if [ ! -x "$JAVACMD" ] ; then 127 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 128 | 129 | Please set the JAVA_HOME variable in your environment to match the 130 | location of your Java installation." 131 | fi 132 | else 133 | JAVACMD=java 134 | if ! command -v java >/dev/null 2>&1 135 | then 136 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | fi 142 | 143 | # Increase the maximum file descriptors if we can. 144 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 145 | case $MAX_FD in #( 146 | max*) 147 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 148 | # shellcheck disable=SC2039,SC3045 149 | MAX_FD=$( ulimit -H -n ) || 150 | warn "Could not query maximum file descriptor limit" 151 | esac 152 | case $MAX_FD in #( 153 | '' | soft) :;; #( 154 | *) 155 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 156 | # shellcheck disable=SC2039,SC3045 157 | ulimit -n "$MAX_FD" || 158 | warn "Could not set maximum file descriptor limit to $MAX_FD" 159 | esac 160 | fi 161 | 162 | # Collect all arguments for the java command, stacking in reverse order: 163 | # * args from the command line 164 | # * the main class name 165 | # * -classpath 166 | # * -D...appname settings 167 | # * --module-path (only if needed) 168 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 169 | 170 | # For Cygwin or MSYS, switch paths to Windows format before running java 171 | if "$cygwin" || "$msys" ; then 172 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 173 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 174 | 175 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 176 | 177 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 178 | for arg do 179 | if 180 | case $arg in #( 181 | -*) false ;; # don't mess with options #( 182 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 183 | [ -e "$t" ] ;; #( 184 | *) false ;; 185 | esac 186 | then 187 | arg=$( cygpath --path --ignore --mixed "$arg" ) 188 | fi 189 | # Roll the args list around exactly as many times as the number of 190 | # args, so each arg winds up back in the position where it started, but 191 | # possibly modified. 192 | # 193 | # NB: a `for` loop captures its iteration list before it begins, so 194 | # changing the positional parameters here affects neither the number of 195 | # iterations, nor the values presented in `arg`. 196 | shift # remove old arg 197 | set -- "$@" "$arg" # push replacement arg 198 | done 199 | fi 200 | 201 | 202 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 203 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 204 | 205 | # Collect all arguments for the java command: 206 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 207 | # and any embedded shellness will be escaped. 208 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 209 | # treated as '${Hostname}' itself on the command line. 210 | 211 | set -- \ 212 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 213 | -classpath "$CLASSPATH" \ 214 | org.gradle.wrapper.GradleWrapperMain \ 215 | "$@" 216 | 217 | # Stop when "xargs" is not available. 218 | if ! command -v xargs >/dev/null 2>&1 219 | then 220 | die "xargs is not available" 221 | fi 222 | 223 | # Use "xargs" to parse quoted args. 224 | # 225 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 226 | # 227 | # In Bash we could simply go: 228 | # 229 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 230 | # set -- "${ARGS[@]}" "$@" 231 | # 232 | # but POSIX shell has neither arrays nor command substitution, so instead we 233 | # post-process each arg (as a line of input to sed) to backslash-escape any 234 | # character that might be a shell metacharacter, then use eval to reverse 235 | # that process (while maintaining the separation between arguments), and wrap 236 | # the whole thing up as a single "set" statement. 237 | # 238 | # This will of course break if any of these variables contains a newline or 239 | # an unmatched quote. 240 | # 241 | 242 | eval "set -- $( 243 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 244 | xargs -n1 | 245 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 246 | tr '\n' ' ' 247 | )" '"$@"' 248 | 249 | exec "$JAVACMD" "$@" 250 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /install.bat: -------------------------------------------------------------------------------- 1 | cd "%~dp0" 2 | rd /s /q "./BTW_dev" 3 | call gradlew.bat --no-daemon downloadAssets 4 | mkdir BTW_dev 5 | tar.exe -xf custom_mappings/mappings_152_converted_yarn_intermediates.zip -C custom_mappings 6 | java -jar libs/tiny-remapper-0.8.6+local-fat.jar %~f1 "BTW_dev/%~nx1" custom_mappings/mappings/mappings.tiny intermediary named %userprofile%/.gradle/caches/fabric-loom/minecraft-1.5.2-intermediary-null.unspecified-1.0.jar 7 | tar.exe -xf %userprofile%/.gradle/caches/fabric-loom/1.5.2-mapped-null.unspecified-1.0/minecraft-1.5.2-mapped-null.unspecified-1.0.jar -C BTW_dev 8 | tar.exe -xf "BTW_dev/%~nx1" -C BTW_dev 9 | call gradlew.bat --no-daemon btwJar 10 | PAUSE -------------------------------------------------------------------------------- /libs/paulscode.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BTW-Community/Cursed-BTW/d8b7d785d1937f547ff0c35d9d7bdb7dff994478/libs/paulscode.zip -------------------------------------------------------------------------------- /libs/tiny-remapper-0.8.6+local-fat.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BTW-Community/Cursed-BTW/d8b7d785d1937f547ff0c35d9d7bdb7dff994478/libs/tiny-remapper-0.8.6+local-fat.jar -------------------------------------------------------------------------------- /mvn/btw/community/mappings/1.0.3/mappings-1.0.3.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BTW-Community/Cursed-BTW/d8b7d785d1937f547ff0c35d9d7bdb7dff994478/mvn/btw/community/mappings/1.0.3/mappings-1.0.3.jar -------------------------------------------------------------------------------- /mvn/btw/community/mappings/1.0.3/mappings-1.0.3.pom: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | btw.community 4 | mappings 5 | 1.0.3 6 | -------------------------------------------------------------------------------- /mvn/net/devtech/grossfabrichacks/4.5/grossfabrichacks-4.5.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BTW-Community/Cursed-BTW/d8b7d785d1937f547ff0c35d9d7bdb7dff994478/mvn/net/devtech/grossfabrichacks/4.5/grossfabrichacks-4.5.jar -------------------------------------------------------------------------------- /mvn/net/devtech/grossfabrichacks/4.5/grossfabrichacks-4.5.pom: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | net.devtech 5 | grossfabrichacks 6 | 4.5 7 | 8 | 9 | net.fabricmc 10 | fabric-loader 11 | + 12 | runtime 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /mvn/net/devtech/grossfabrichacks/6.1-btw/grossfabrichacks-6.1-btw.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BTW-Community/Cursed-BTW/d8b7d785d1937f547ff0c35d9d7bdb7dff994478/mvn/net/devtech/grossfabrichacks/6.1-btw/grossfabrichacks-6.1-btw.jar -------------------------------------------------------------------------------- /mvn/net/devtech/grossfabrichacks/6.1-btw/grossfabrichacks-6.1-btw.pom: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | net.devtech 5 | grossfabrichacks 6 | 6.1-btw 7 | 8 | 9 | net.fabricmc 10 | fabric-loader 11 | + 12 | runtime 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /mvn/net/devtech/grossfabrichacks/6.2-btw/grossfabrichacks-6.2-btw.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BTW-Community/Cursed-BTW/d8b7d785d1937f547ff0c35d9d7bdb7dff994478/mvn/net/devtech/grossfabrichacks/6.2-btw/grossfabrichacks-6.2-btw.jar -------------------------------------------------------------------------------- /mvn/net/devtech/grossfabrichacks/6.2-btw/grossfabrichacks-6.2-btw.pom: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | net.devtech 5 | grossfabrichacks 6 | 6.2-btw 7 | 8 | 9 | net.fabricmc 10 | fabric-loader 11 | + 12 | runtime 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | maven { 4 | url 'https://BTW-Community.github.io/BTW-gradle' 5 | } 6 | maven { 7 | name = 'Fabric' 8 | url = 'https://maven.fabricmc.net/' 9 | } 10 | maven { 11 | name = 'Jitpack' 12 | url = 'https://jitpack.io' 13 | } 14 | gradlePluginPortal() 15 | } 16 | } 17 | rootProject.name = name 18 | 19 | gradle.rootProject { 20 | group = group 21 | version = version 22 | description = description 23 | } -------------------------------------------------------------------------------- /src/main/java/btw/community/fabric/BTWFabricMod.java: -------------------------------------------------------------------------------- 1 | package btw.community.fabric; 2 | 3 | import net.devtech.grossfabrichacks.entrypoints.PrePreLaunch; 4 | import net.devtech.grossfabrichacks.mixin.GrossFabricHacksPlugin; 5 | import net.devtech.grossfabrichacks.transformer.TransformerApi; 6 | import net.devtech.grossfabrichacks.transformer.asm.AsmClassTransformer; 7 | import net.devtech.grossfabrichacks.unsafe.UnsafeUtil; 8 | import net.fabricmc.api.EnvType; 9 | import net.fabricmc.api.Environment; 10 | import net.fabricmc.api.ModInitializer; 11 | import net.fabricmc.loader.api.FabricLoader; 12 | import net.fabricmc.loader.impl.game.minecraft.Hooks; 13 | import net.fabricmc.loader.impl.launch.knot.Knot; 14 | import net.superblaubeere27.asmdelta.ASMDeltaPatch; 15 | import net.superblaubeere27.asmdelta.ASMDeltaTransformer; 16 | import net.superblaubeere27.asmdelta.difference.AbstractDifference; 17 | import org.objectweb.asm.Opcodes; 18 | import org.objectweb.asm.tree.*; 19 | import org.spongepowered.asm.mixin.extensibility.IMixinInfo; 20 | import org.spongepowered.asm.mixin.transformer.ClassInfo; 21 | import sun.misc.Unsafe; 22 | 23 | import java.applet.Applet; 24 | import java.applet.AppletStub; 25 | import java.io.IOException; 26 | import java.io.InputStream; 27 | import java.lang.instrument.Instrumentation; 28 | import java.lang.reflect.InvocationTargetException; 29 | import java.lang.reflect.Method; 30 | import java.util.*; 31 | import java.util.function.Function; 32 | import java.util.stream.Collectors; 33 | 34 | import static org.objectweb.asm.Opcodes.*; 35 | 36 | public class BTWFabricMod implements ModInitializer, PrePreLaunch { 37 | private static boolean DISABLE_BYTECODE_VERIFIER = false; 38 | public static String[] args = null; 39 | private static ASMDeltaTransformer transformer; 40 | private static AsmClassTransformer asmClassTransformer; 41 | 42 | @Override 43 | public void onInitialize() { 44 | } 45 | 46 | public static String getMappedName(String cls) { 47 | return FabricLoader.getInstance().getMappingResolver().mapClassName("intermediary", cls); 48 | } 49 | 50 | public static String getMappedNameField(String cls, String field, String desc) { 51 | return FabricLoader.getInstance().getMappingResolver().mapFieldName("intermediary", cls, field, desc); 52 | } 53 | 54 | public static String getMappedNameMethod(String cls, String method, String desc) { 55 | return FabricLoader.getInstance().getMappingResolver().mapMethodName("intermediary", cls, method, desc); 56 | } 57 | 58 | //@Override 59 | public void onPrePrePreLaunch() { 60 | 61 | } 62 | @Override 63 | public void onPrePreLaunch() { 64 | if (DISABLE_BYTECODE_VERIFIER) { 65 | // This following code will set the verifier to none. 66 | // Because the JVM does not like it when you totally modify the bytecode at runtime. 67 | try { 68 | HashMap structs = getStructs(); 69 | //System.out.println("Structs: " + structs); 70 | HashMap types = getTypes(structs); 71 | //System.out.println("Types: " + types); 72 | List flags = getFlags(types); 73 | //System.out.println("Flags: " + flags); 74 | for (JVMFlag flag : flags) { 75 | if (flag.name.equals("BytecodeVerificationLocal") 76 | || flag.name.equals("BytecodeVerificationRemote")) { 77 | System.out.println(UnsafeUtil.getByte(flag.address)); 78 | } 79 | } 80 | disableBytecodeVerifier(); 81 | structs = getStructs(); 82 | //System.out.println("Structs: " + structs); 83 | types = getTypes(structs); 84 | //System.out.println("Types: " + types); 85 | flags = getFlags(types); 86 | //System.out.println("Flags: " + flags); 87 | for (JVMFlag flag : flags) { 88 | if (flag.name.equals("BytecodeVerificationLocal") 89 | || flag.name.equals("BytecodeVerificationRemote")) { 90 | System.out.println(UnsafeUtil.getByte(flag.address)); 91 | } 92 | } 93 | 94 | } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { 95 | throw new RuntimeException(e); 96 | } 97 | } 98 | 99 | agentmain("", null); 100 | } 101 | 102 | private void disableBytecodeVerifier() throws InvocationTargetException, NoSuchMethodException, IllegalAccessException { 103 | List flags = getFlags(getTypes(getStructs())); 104 | 105 | for (JVMFlag flag : flags) { 106 | if (flag.name.equals("BytecodeVerificationLocal") 107 | || flag.name.equals("BytecodeVerificationRemote")) { 108 | UnsafeUtil.putByte(flag.address, (byte) 0); 109 | } 110 | } 111 | } 112 | 113 | private void enableBytecodeVerifier() throws InvocationTargetException, NoSuchMethodException, IllegalAccessException { 114 | Unsafe theUnsafe = (Unsafe) UnsafeUtil.theUnsafe; 115 | List flags = getFlags(getTypes(getStructs())); 116 | 117 | for (JVMFlag flag : flags) { 118 | if (flag.name.equals("BytecodeVerificationRemote")) { 119 | theUnsafe.putByte(flag.address, (byte) 1); 120 | } 121 | } 122 | } 123 | 124 | private long findNative(String name) { 125 | try { 126 | Method findNative = ClassLoader.class.getDeclaredMethod("findNative", ClassLoader.class, String.class); 127 | findNative.setAccessible(true); 128 | return (long) findNative.invoke(null, null, name); 129 | } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { 130 | throw new RuntimeException(e); 131 | } 132 | } 133 | 134 | HashMap getStructs() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { 135 | // get findNative reference from ClassLoader.class 136 | Method findNative = ClassLoader.class.getDeclaredMethod("findNative", ClassLoader.class, String.class); 137 | Method loadLibrary = ClassLoader.class.getDeclaredMethod("loadLibrary", Class.class, String.class, boolean.class); 138 | Method initializePath = ClassLoader.class.getDeclaredMethod("initializePath", String.class); 139 | findNative.setAccessible(true); 140 | initializePath.setAccessible(true); 141 | loadLibrary.setAccessible(true); 142 | loadLibrary.invoke(null, null, "server/jvm", false); 143 | //System.out.println("findNative: " + findNative.invoke(null, null, "gHotSpotVMStructs")); 144 | // get the field offset 145 | long fieldOffset = UnsafeUtil.getLong((long) findNative.invoke(null, null, "gHotSpotVMStructs")); 146 | Function symbol = (String name) -> { 147 | try { 148 | return UnsafeUtil.getLong((Long) findNative.invoke(null, null, name)); 149 | } catch (IllegalAccessException | InvocationTargetException e) { 150 | throw new RuntimeException(e); 151 | } 152 | }; 153 | Function offsetSymbol = name -> symbol.apply("gHotSpotVMStructEntry" + name + "Offset"); 154 | Function derefReadString = addr -> { 155 | Long l = UnsafeUtil.getLong(addr); 156 | StringBuilder sb = new StringBuilder(); 157 | if (l != 0) { 158 | while (true) { 159 | char c = (char) UnsafeUtil.getByte(l); 160 | if (c == 0) 161 | break; 162 | sb.append(c); 163 | l += 1; 164 | } 165 | } 166 | return sb.toString(); 167 | }; 168 | 169 | Long currentEntry = symbol.apply("gHotSpotVMStructs"); 170 | Long arrayStride = symbol.apply("gHotSpotVMStructEntryArrayStride"); 171 | 172 | HashMap structs = new HashMap(); 173 | while (true) { 174 | String typeName = derefReadString.apply(currentEntry + offsetSymbol.apply("TypeName")); 175 | String fieldName = derefReadString.apply(currentEntry + offsetSymbol.apply("FieldName")); 176 | if (typeName == null || fieldName == null || typeName.isEmpty() || fieldName.isEmpty()) 177 | break; 178 | //System.out.println(typeName + " " + fieldName); 179 | String typeString = derefReadString.apply(currentEntry + offsetSymbol.apply("TypeString")); 180 | boolean isStatic = UnsafeUtil.getInt(currentEntry + offsetSymbol.apply("IsStatic")) != 0; 181 | 182 | Long offsetOffset = isStatic ? offsetSymbol.apply("Address") : offsetSymbol.apply("Offset"); 183 | Long offset = UnsafeUtil.getLong(currentEntry + offsetOffset); 184 | 185 | structs.putIfAbsent(typeName, new JVMStruct(typeName)); 186 | JVMStruct struct = structs.get(typeName); 187 | struct.set(fieldName, new BTWFabricMod.Field(fieldName, typeString, offset, isStatic)); 188 | 189 | currentEntry += arrayStride; 190 | } 191 | return structs; 192 | } 193 | 194 | HashMap getTypes(HashMap structs) { 195 | Function symbol = (String name) -> { 196 | return UnsafeUtil.getLong((Long) findNative(name)); 197 | }; 198 | Function offsetSymbol = name -> symbol.apply("gHotSpotVMTypeEntry" + name + "Offset"); 199 | Function derefReadString = addr -> { 200 | Long l = UnsafeUtil.getLong(addr); 201 | StringBuilder sb = new StringBuilder(); 202 | if (l != 0) { 203 | while (true) { 204 | char c = (char) UnsafeUtil.getByte(l); 205 | if (c == 0) 206 | break; 207 | sb.append(c); 208 | l += 1; 209 | } 210 | } 211 | return sb.toString(); 212 | }; 213 | 214 | Long entry = symbol.apply("gHotSpotVMTypes"); 215 | Long arrayStride = symbol.apply("gHotSpotVMTypeEntryArrayStride"); 216 | 217 | HashMap types = new HashMap(); 218 | 219 | while (true) { 220 | String typeName = derefReadString.apply(entry + offsetSymbol.apply("TypeName")); 221 | if (typeName == null || typeName.isEmpty()) break; 222 | 223 | String superClassName = derefReadString.apply(entry + offsetSymbol.apply("SuperclassName")); 224 | 225 | //System.out.println(typeName + " " + superClassName); 226 | 227 | int size = UnsafeUtil.getInt(entry + offsetSymbol.apply("Size")); 228 | boolean oop = UnsafeUtil.getInt(entry + offsetSymbol.apply("IsOopType")) != 0; 229 | boolean int_ = UnsafeUtil.getInt(entry + offsetSymbol.apply("IsIntegerType")) != 0; 230 | boolean unsigned = UnsafeUtil.getInt(entry + offsetSymbol.apply("IsUnsigned")) != 0; 231 | 232 | if (structs.get(typeName) != null) { 233 | HashMap structFields = structs.get(typeName).fields; 234 | JVMType t = new JVMType( 235 | typeName, superClassName, size, 236 | oop, int_, unsigned 237 | ); 238 | types.put(typeName, t); 239 | 240 | if (structFields != null) 241 | t.fields.putAll(structFields); 242 | } 243 | 244 | entry += arrayStride; 245 | } 246 | 247 | return types; 248 | } 249 | 250 | List getFlags(HashMap types) { 251 | ArrayList jvmFlags = new ArrayList(); 252 | 253 | JVMType flagType = types.get("Flag") != null ? types.get("Flag") : types.get("JVMFlag"); 254 | if (flagType == null) { 255 | throw new RuntimeException("Could not resolve type 'Flag'"); 256 | } 257 | 258 | BTWFabricMod.Field flagsField = flagType.fields.get("flags"); 259 | if (flagsField == null) { 260 | throw new RuntimeException("Could not resolve field 'Flag.flags'"); 261 | } 262 | long flags = UnsafeUtil.getAddress(flagsField.offset); 263 | 264 | BTWFabricMod.Field numFlagsField = flagType.fields.get("numFlags"); 265 | int numFlags = UnsafeUtil.getInt(numFlagsField.offset); 266 | 267 | BTWFabricMod.Field nameField = flagType.fields.get("_name"); 268 | 269 | BTWFabricMod.Field addrField = flagType.fields.get("_addr"); 270 | 271 | for (long i = 0; i < numFlags; i++) { 272 | long flagAddress = flags + (i * flagType.size); 273 | long flagValueAddress = UnsafeUtil.getAddress(flagAddress + addrField.offset); 274 | long flagNameAddress = UnsafeUtil.getAddress(flagAddress + nameField.offset); 275 | 276 | String flagName; 277 | StringBuilder sb = new StringBuilder(); 278 | if (flagNameAddress != 0) { 279 | while (true) { 280 | char c = (char) UnsafeUtil.getByte(flagNameAddress); 281 | if (c == 0) 282 | break; 283 | sb.append(c); 284 | flagNameAddress += 1; 285 | } 286 | } 287 | flagName = sb.toString(); 288 | if (flagName != null && !flagName.isEmpty()) { 289 | JVMFlag flag = new JVMFlag(flagName, flagValueAddress); 290 | jvmFlags.add(flag); 291 | } 292 | } 293 | 294 | return jvmFlags; 295 | } 296 | 297 | class JVMFlag /*(val name: String, val address: Long)*/ { 298 | String name; 299 | Long address; 300 | 301 | public JVMFlag(String name, Long address) { 302 | this.name = name; 303 | this.address = address; 304 | } 305 | } 306 | 307 | class JVMType { 308 | 309 | String type; 310 | String superClass; 311 | int size; 312 | boolean oop; 313 | boolean int_; 314 | boolean unsigned; 315 | 316 | HashMap fields = new HashMap(); 317 | 318 | public JVMType(String type, String superClass, int size, boolean oop, boolean int_, boolean unsigned) { 319 | this.type = type; 320 | this.superClass = superClass; 321 | this.size = size; 322 | this.oop = oop; 323 | this.int_ = int_; 324 | this.unsigned = unsigned; 325 | } 326 | } 327 | 328 | class JVMStruct { 329 | private final String name; 330 | 331 | public JVMStruct(String name) { 332 | this.name = name; 333 | } 334 | 335 | HashMap fields = new HashMap(); 336 | 337 | public BTWFabricMod.Field get(String f) { 338 | return fields.get(f); 339 | } 340 | public void set(String f, BTWFabricMod.Field value) { 341 | fields.put(f, value); 342 | } 343 | 344 | 345 | } 346 | private class Field { 347 | public final String name; 348 | public final String type; 349 | public final Long offset; 350 | public final Boolean isStatic; 351 | 352 | public Field(String name, String type, Long offset, Boolean isStatic) { 353 | this.name = name; 354 | this.type = type; 355 | this.offset = offset; 356 | this.isStatic = isStatic; 357 | } 358 | } 359 | 360 | //@Override 361 | public void onPreLaunch() { 362 | 363 | } 364 | 365 | public static void agentmain(String agentArgs, Instrumentation inst) { 366 | ASMDeltaPatch patch; 367 | 368 | try { 369 | if (FabricLoader.getInstance().isDevelopmentEnvironment()) 370 | try (InputStream inputStream = BTWFabricMod.class.getResourceAsStream("/output-client-development.patch")) { 371 | patch = ASMDeltaPatch.read(inputStream); 372 | } 373 | else try (InputStream inputStream = Knot.getLauncher().getEnvironmentType() == EnvType.CLIENT ? BTWFabricMod.class.getResourceAsStream("/output-client-release.patch") : BTWFabricMod.class.getResourceAsStream("/output-server-release.patch")) { 374 | patch = ASMDeltaPatch.read(inputStream); 375 | } 376 | } catch (IOException e) { 377 | throw new IllegalStateException("Failed to read patch"); 378 | } 379 | 380 | HashMap> patchMap = new HashMap<>(); 381 | 382 | // Grouping patches by class names 383 | for (AbstractDifference abstractDifference : patch.getDifferenceList()) { 384 | patchMap.computeIfAbsent(abstractDifference.getClassName().replace('/', '.'), e -> new HashSet<>()).add(abstractDifference); 385 | } 386 | 387 | System.out.println("Loaded " + patch.getDifferenceList().size() + " patches for " + patchMap.size() + " classes"); 388 | System.out.println("Applying patch " + patch.getPatchName()); 389 | 390 | try { 391 | transformer = new ASMDeltaTransformer(patchMap); 392 | asmClassTransformer = new AsmClassTransformer() { 393 | @Override 394 | public void transform(String name, ClassNode node) { 395 | String classNameMC = getMappedName("net.minecraft.client.main.Main"); 396 | String classNameMinecraft = getMappedName("net.minecraft.class_1600"); 397 | String classNameDedicatedServer = getMappedName("net.minecraft.class_770"); 398 | //String classNameMinecraftServer = getMappedName("net.minecraft.server.MinecraftServer"); 399 | 400 | String startGameName = getMappedNameMethod("net.minecraft.class_1600", "method_2921", "()V"); 401 | String gameDirName = getMappedNameField("net.minecraft.class_1600", "field_3762", "Ljava/io/File;"); 402 | 403 | if (name.equals(classNameMC) || name.equals(classNameMC.replace('.', '/'))) { 404 | System.out.println("Found Main class"); 405 | for (MethodNode method : node.methods) { 406 | // Inject main into constructor 407 | if ("start".equals(method.name)) { 408 | // Find first return statement 409 | for (AbstractInsnNode insn = method.instructions.getFirst(); insn != null; insn = insn.getNext()) { 410 | if (insn.getOpcode() == RETURN) { 411 | InsnList list = new InsnList(); 412 | // wait for this.mcThread to finish 413 | list.add(new VarInsnNode(ALOAD, 0)); 414 | list.add(new FieldInsnNode(GETFIELD, classNameMC.replace('.', '/'), "mcThread", "Ljava/lang/Thread;")); 415 | list.add(new MethodInsnNode(INVOKEVIRTUAL, "java/lang/Thread", "join", "()V", false)); 416 | 417 | method.instructions.insertBefore(insn, list); 418 | break; 419 | } 420 | } 421 | } 422 | } 423 | } 424 | if (name.equals(classNameMinecraft) || name.equals(classNameMinecraft.replace('.', '/'))) { 425 | System.out.println("Found Minecraft class"); 426 | for (MethodNode method : node.methods) { 427 | if (startGameName.equals(method.name)) { 428 | // Find first return statement 429 | for (AbstractInsnNode insn = method.instructions.getFirst(); insn != null; insn = insn.getNext()) { 430 | if (insn.getOpcode() == INVOKESTATIC && ((MethodInsnNode) insn).name.equals("setResizable") && ((MethodInsnNode) insn).desc.equals("(Z)V") && ((MethodInsnNode) insn).owner.equals("org/lwjgl/opengl/Display")) { 431 | InsnList list = new InsnList(); 432 | // get net/minecraft/class_1600.field_3762 Ljava/io/File; (not static) 433 | list.add(new VarInsnNode(Opcodes.ALOAD, 0)); 434 | list.add(new FieldInsnNode(Opcodes.GETFIELD, classNameMinecraft.replace('.', '/'), gameDirName, "Ljava/io/File;")); 435 | list.add(new VarInsnNode(Opcodes.ALOAD, 0)); 436 | list.add(new MethodInsnNode(Opcodes.INVOKESTATIC, Hooks.INTERNAL_NAME, "startClient", "(Ljava/io/File;Ljava/lang/Object;)V", false)); 437 | method.instructions.insertBefore(insn.getNext(), list); 438 | System.out.println("Injected startClient call"); 439 | break; 440 | } 441 | } 442 | } 443 | } 444 | } 445 | if (name.equals(classNameDedicatedServer) || name.equals(classNameDedicatedServer.replace('.', '/'))) { 446 | System.out.println("Found Dedicated Minecraft Server class"); 447 | for (MethodNode method : node.methods) { 448 | // Inject main into constructor 449 | if ("".equals(method.name) && method.desc.equals("(Ljava/io/File;)V")) { 450 | // Find first return statement 451 | for (AbstractInsnNode insn = method.instructions.getFirst(); insn != null; insn = insn.getNext()) { 452 | if (insn.getOpcode() == RETURN) { 453 | InsnList list = new InsnList(); 454 | // get net/minecraft/class_1600.field_3762 Ljava/io/File; (not static) 455 | list.add(new VarInsnNode(ALOAD, 1)); 456 | list.add(new VarInsnNode(ALOAD, 0)); 457 | list.add(new MethodInsnNode(Opcodes.INVOKESTATIC, Hooks.INTERNAL_NAME, "startServer", "(Ljava/io/File;Ljava/lang/Object;)V", false)); 458 | method.instructions.insertBefore(insn, list); 459 | break; 460 | } 461 | } 462 | } 463 | } 464 | } 465 | } 466 | }; 467 | 468 | //TransformerApi.registerPreMixinAsmClassTransformer(asmClassTransformer); 469 | TransformerApi.registerPreMixinRawClassTransformer(transformer); 470 | TransformerApi.registerNullClassTransformer(transformer); 471 | } catch (Throwable e) { 472 | System.err.println("Failed to initialize agent"); 473 | e.printStackTrace(); 474 | } 475 | if (GrossFabricHacksPlugin.preApplyList == null) { 476 | GrossFabricHacksPlugin.preApplyList = new ArrayList<>(); 477 | } 478 | try { 479 | GrossFabricHacksPlugin.preApplyList.add(BTWFabricMod.class.getMethod("performPreApplyOperation", String.class, ClassNode.class, String.class, IMixinInfo.class)); 480 | } catch (NoSuchMethodException e) { 481 | throw new RuntimeException(e); 482 | } 483 | } 484 | 485 | public static void performPreApplyOperation(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { 486 | transformer.doTransform2(targetClassName, targetClass, false); 487 | 488 | // Use reflection to get MixinInfo.targetClasses 489 | ArrayList targetClasses; 490 | Set methods; 491 | Set fields; 492 | try { 493 | java.lang.reflect.Field f = mixinInfo.getClass().getDeclaredField("targetClasses"); 494 | f.setAccessible(true); 495 | targetClasses = (ArrayList) f.get(mixinInfo); 496 | f = ClassInfo.class.getDeclaredField("methods"); 497 | f.setAccessible(true); 498 | 499 | java.lang.reflect.Field f2 = ClassInfo.class.getDeclaredField("fields"); 500 | f2.setAccessible(true); 501 | for (ClassInfo ci : targetClasses) { 502 | if (ci.getClassName().equals(targetClassName)) { 503 | methods = (Set) f.get(ci); 504 | methods.clear(); 505 | methods.addAll(targetClass.methods.stream().map(m -> ci.new Method(m)).collect(Collectors.toSet())); 506 | 507 | fields = (Set) f2.get(ci); 508 | fields.clear(); 509 | fields.addAll(targetClass.fields.stream().map(m -> ci.new Field(m)).collect(Collectors.toSet())); 510 | } 511 | } 512 | } catch (NoSuchFieldException | IllegalAccessException e) { 513 | throw new RuntimeException(e); 514 | } 515 | 516 | asmClassTransformer.transform(targetClassName, targetClass); 517 | } 518 | } 519 | -------------------------------------------------------------------------------- /src/main/java/btw/community/fabric/mixin/MinecraftMixin.java: -------------------------------------------------------------------------------- 1 | package btw.community.fabric.mixin; 2 | 3 | import net.minecraft.src.Minecraft; 4 | import org.spongepowered.asm.mixin.Mixin; 5 | import org.spongepowered.asm.mixin.injection.At; 6 | import org.spongepowered.asm.mixin.injection.Inject; 7 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 8 | 9 | @Mixin(Minecraft.class) 10 | public class MinecraftMixin { 11 | @Inject(method = "", at = @At("RETURN")) 12 | private void onInit(CallbackInfo info) { 13 | //while (true) { 14 | System.out.println("Hello from MinecraftMixin!"); 15 | //} 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/btw/community/fabric/mixin/MinecraftServerMixin.java: -------------------------------------------------------------------------------- 1 | package btw.community.fabric.mixin; 2 | 3 | import net.minecraft.server.MinecraftServer; 4 | import org.spongepowered.asm.mixin.Mixin; 5 | import org.spongepowered.asm.mixin.injection.At; 6 | import org.spongepowered.asm.mixin.injection.Inject; 7 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 8 | 9 | @Mixin(MinecraftServer.class) 10 | public class MinecraftServerMixin { 11 | @Inject(method = "", at = @At("RETURN")) 12 | private void onServerStart(CallbackInfo ci) { 13 | System.out.println("Hello BTW server!"); 14 | //throw new RuntimeException("BTW server is not supported yet!"); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/btw/community/fabric/mixin/NetClientHandlerMixin.java: -------------------------------------------------------------------------------- 1 | package btw.community.fabric.mixin; 2 | 3 | import net.minecraft.src.Minecraft; 4 | import net.minecraft.src.NetClientHandler; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.injection.At; 7 | import org.spongepowered.asm.mixin.injection.Inject; 8 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 9 | 10 | @Mixin(NetClientHandler.class) 11 | public class NetClientHandlerMixin { 12 | @Inject(method = "isTerrainAroundPlayerLoaded", remap = false, at = @At("HEAD"), cancellable = true) 13 | private void isTerrainAroundPlayerLoadedInject(CallbackInfoReturnable cir) { 14 | if (Minecraft.getMinecraft().thePlayer == null) { 15 | cir.setReturnValue(false); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/net/superblaubeere27/asmdelta/ASMDelta.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 superblaubeere27 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | * 6 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | */ 10 | 11 | package net.superblaubeere27.asmdelta; 12 | 13 | import net.superblaubeere27.asmdelta.difference.AbstractDifference; 14 | import net.superblaubeere27.asmdelta.difference.clazz.*; 15 | import net.superblaubeere27.asmdelta.difference.fields.*; 16 | import net.superblaubeere27.asmdelta.difference.methods.*; 17 | import net.superblaubeere27.asmdelta.utils.InstructionComparator; 18 | import net.superblaubeere27.asmdelta.utils.ScheduledRunnable; 19 | import net.superblaubeere27.asmdelta.utils.Scheduler; 20 | import org.objectweb.asm.ClassReader; 21 | import org.objectweb.asm.tree.ClassNode; 22 | import org.objectweb.asm.tree.FieldNode; 23 | import org.objectweb.asm.tree.LocalVariableNode; 24 | import org.objectweb.asm.tree.MethodNode; 25 | 26 | import java.io.File; 27 | import java.io.IOException; 28 | import java.io.InputStream; 29 | import java.util.*; 30 | import java.util.stream.Collectors; 31 | import java.util.zip.ZipEntry; 32 | import java.util.zip.ZipFile; 33 | 34 | public class ASMDelta { 35 | 36 | public static List calculateDifference(int threadCount, File origFile, File newFile) throws IOException { 37 | HashMap originalClasses = loadJar(threadCount, loadClasspathFile(origFile)); 38 | HashMap newClasses = loadJar(threadCount, loadClasspathFile(newFile)); 39 | 40 | List differences = new ArrayList<>(); 41 | 42 | originalClasses.keySet().stream().filter(key -> !newClasses.containsKey(key)).map(RemoveClassDifference::new).forEach(differences::add); // Removed classes 43 | newClasses.keySet().stream().filter(key -> !originalClasses.containsKey(key)).map(key -> new AddClassDifference(newClasses.get(key))).forEach(differences::add); // Added classes 44 | 45 | LinkedList collect = originalClasses.keySet().stream().filter(newClasses::containsKey).distinct().collect(Collectors.toCollection(LinkedList::new)); 46 | 47 | ScheduledRunnable runnable = () -> { 48 | String next; 49 | 50 | synchronized (collect) { 51 | next = collect.poll(); 52 | } 53 | 54 | if (next == null) return true; 55 | 56 | compareClasses(differences, originalClasses.get(next), newClasses.get(next)); 57 | 58 | return false; 59 | }; 60 | 61 | Scheduler scheduler = new Scheduler(runnable); 62 | 63 | scheduler.run(threadCount); 64 | scheduler.waitFor(); 65 | 66 | return differences; 67 | } 68 | 69 | private static void compareClasses(List differences, ClassNode oldClass, ClassNode newClass) { 70 | if (oldClass.access != newClass.access) { 71 | differences.add(new ClassAccessDifference(oldClass.name, oldClass.access, newClass.access)); 72 | } 73 | 74 | ClassMetadataDifference metadataDifference = ClassMetadataDifference.createNew( 75 | oldClass.name, 76 | !Objects.equals(oldClass.outerClass, newClass.outerClass) ? newClass.outerClass : null, 77 | !Objects.equals(oldClass.nestHostClass, newClass.nestHostClass) ? newClass.nestHostClass : null, 78 | !Objects.equals(oldClass.outerMethod, newClass.outerMethod) ? newClass.outerMethod : null, 79 | !Objects.equals(oldClass.outerMethodDesc, newClass.outerMethodDesc) ? newClass.outerMethodDesc : null, 80 | !Objects.equals(oldClass.signature, newClass.signature) ? newClass.signature : null, 81 | !Objects.equals(oldClass.sourceDebug, newClass.sourceDebug) ? newClass.sourceDebug : null, 82 | !Objects.equals(oldClass.sourceFile, newClass.sourceFile) ? newClass.sourceFile : null, 83 | !Objects.equals(oldClass.superName, newClass.superName) ? newClass.superName : null, 84 | !oldClass.interfaces.equals(newClass.interfaces) ? newClass.interfaces : null 85 | ); 86 | 87 | if (metadataDifference != null) { 88 | differences.add(metadataDifference); 89 | } 90 | 91 | if (oldClass.version != newClass.version) { 92 | differences.add(new ClassVersionDifference(oldClass.name, newClass.version)); 93 | } 94 | 95 | // TODO Implement annotations ._. 96 | 97 | // 98 | 99 | { 100 | HashMap oldFields = new HashMap<>(); 101 | 102 | if (oldClass.fields != null) { 103 | for (FieldNode field : oldClass.fields) { 104 | oldFields.put(field.name, field); 105 | } 106 | } 107 | HashMap newFields = new HashMap<>(); 108 | 109 | if (newClass.fields != null) { 110 | for (FieldNode field : newClass.fields) { 111 | newFields.put(field.name, field); 112 | } 113 | } 114 | 115 | oldFields.keySet().stream().filter(key -> !newFields.containsKey(key)).map(key -> new RemoveFieldDifference(oldClass.name, key)).forEach(differences::add); // Removed fields 116 | newFields.keySet().stream().filter(key -> !oldFields.containsKey(key)).map(key -> new AddFieldDifference(oldClass.name, newFields.get(key))).forEach(differences::add); // Added fields 117 | 118 | oldFields.keySet().stream().filter(newFields::containsKey).forEach(key -> compareField(differences, oldClass.name, oldFields.get(key), newFields.get(key))); 119 | } 120 | 121 | // 122 | 123 | // 124 | 125 | { 126 | HashMap oldMethods = new HashMap<>(); 127 | 128 | if (oldClass.fields != null) { 129 | for (MethodNode method : oldClass.methods) { 130 | oldMethods.put(method.name + method.desc, method); 131 | } 132 | } 133 | HashMap newMethods = new HashMap<>(); 134 | 135 | if (newClass.fields != null) { 136 | for (MethodNode method : newClass.methods) { 137 | newMethods.put(method.name + method.desc, method); 138 | } 139 | } 140 | 141 | oldMethods.keySet().stream().filter(key -> !newMethods.containsKey(key)).map(key -> { 142 | MethodNode methodNode = oldMethods.get(key); 143 | 144 | return new RemoveMethodDifference(oldClass.name, methodNode.name, methodNode.desc); 145 | }).forEach(differences::add); // Removed fields 146 | 147 | newMethods.keySet().stream().filter(key -> !oldMethods.containsKey(key)).map(key -> new AddMethodDifference(oldClass.name, newMethods.get(key))).forEach(differences::add); // Added fields 148 | 149 | oldMethods.keySet().stream().filter(newMethods::containsKey).forEach(key -> compareMethod(differences, oldClass.name, oldMethods.get(key), newMethods.get(key))); 150 | } 151 | 152 | // 153 | 154 | 155 | } 156 | 157 | private static void compareMethod(List differences, String className, MethodNode oldMethod, MethodNode newMethod) { 158 | if (oldMethod.access != newMethod.access) { 159 | differences.add(new MethodAccessDifference(className, oldMethod.name, oldMethod.desc, oldMethod.access, newMethod.access)); 160 | } 161 | if (!Objects.equals(oldMethod.signature, newMethod.signature)) { // FieldNode.signature might be null 162 | differences.add(new MethodSignatureDifference(className, oldMethod.name, oldMethod.desc, newMethod.signature)); 163 | } 164 | if (!isSameLocalVariables(oldMethod.localVariables, newMethod.localVariables)) { 165 | differences.add(new MethodLocalVariableDifference(className, oldMethod.name, oldMethod.desc, newMethod.localVariables)); 166 | } 167 | if (!(oldMethod.exceptions == null ? Collections.emptyList() : oldMethod.exceptions).equals(newMethod.exceptions == null ? Collections.emptyList() : newMethod.exceptions)) { 168 | differences.add(new MethodExceptionDifference(className, oldMethod.name, oldMethod.desc, newMethod.exceptions)); 169 | } 170 | if (oldMethod.annotationDefault != newMethod.annotationDefault) { 171 | differences.add(new MethodAnnotationDefaultDifference(className, oldMethod.name, oldMethod.desc, newMethod.annotationDefault)); 172 | } 173 | if (oldMethod.maxLocals != newMethod.maxLocals || oldMethod.maxStack != newMethod.maxStack) { 174 | differences.add(new MethodMaxsDifference(className, oldMethod.name, oldMethod.desc, newMethod.maxStack, newMethod.maxLocals)); 175 | } 176 | if (!InstructionComparator.isSame(oldMethod.instructions, newMethod.instructions, oldMethod.tryCatchBlocks, newMethod.tryCatchBlocks)) { 177 | differences.add(new MethodInstructionDifference(className, oldMethod.name, oldMethod.desc, newMethod)); 178 | differences.add(new MethodLocalVariableDifference(className, oldMethod.name, oldMethod.desc, newMethod.localVariables)); 179 | } 180 | } 181 | 182 | private static void compareField(List differences, String className, FieldNode oldField, FieldNode newField) { 183 | if (oldField.access != newField.access) { 184 | differences.add(new FieldAccessDifference(className, oldField.name, oldField.access, newField.access)); 185 | } 186 | if (!oldField.desc.equals(newField.desc)) { 187 | differences.add(new FieldDescriptionDifference(className, oldField.name, newField.desc)); 188 | } 189 | if (!Objects.equals(oldField.signature, newField.signature)) { // FieldNode.signature might be null 190 | differences.add(new FieldSignatureDifference(className, oldField.name, newField.signature)); 191 | } 192 | if (!Objects.equals(oldField.value, newField.value)) { 193 | differences.add(new FieldValueDifference(className, oldField.name, newField.value)); 194 | } 195 | // TODO Implement annotation stuff 196 | } 197 | 198 | private static boolean isSameLocalVariables(List a, List b) { 199 | if (a == null && b == null) return true; 200 | if (a == null && b.isEmpty()) return true; 201 | if (b == null && a.isEmpty()) return true; 202 | if (a.size() > 0 || b.size() > 0) { 203 | System.out.println(a); 204 | System.out.println(b); 205 | System.out.println("-------"); 206 | } 207 | if (a.size() != b.size()) return false; 208 | 209 | for (int i = 0; i < a.size(); i++) { 210 | LocalVariableNode aNode = a.get(i); 211 | LocalVariableNode bNode = b.get(i); 212 | 213 | if (!Objects.equals(aNode.name, bNode.name) 214 | || !Objects.equals(aNode.desc, bNode.desc) 215 | || !Objects.equals(aNode.signature, bNode.signature) 216 | || aNode.index != bNode.index) { 217 | return false; 218 | } 219 | } 220 | 221 | return true; 222 | } 223 | 224 | public static HashMap loadJar(int threadCount, List bytes1) { 225 | LinkedList byteList = new LinkedList<>(bytes1); 226 | HashMap loaded = new HashMap<>(); 227 | 228 | ScheduledRunnable runnable = () -> { 229 | Map map = new HashMap<>(); 230 | 231 | while (true) { 232 | byte[] bytes; 233 | 234 | synchronized (byteList) { 235 | bytes = byteList.poll(); 236 | } 237 | 238 | if (bytes == null) break; 239 | 240 | ClassReader reader = new ClassReader(bytes); 241 | ClassNode node = new ClassNode(); 242 | //reader.accept(node, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); 243 | //reader.accept(node, ClassReader.SKIP_DEBUG); 244 | reader.accept(node, ClassReader.EXPAND_FRAMES); 245 | map.put(node.name, node); 246 | } 247 | 248 | synchronized (loaded) { 249 | loaded.putAll(map); 250 | } 251 | 252 | return true; 253 | }; 254 | 255 | Scheduler scheduler = new Scheduler(runnable); 256 | 257 | scheduler.run(threadCount); 258 | scheduler.waitFor(); 259 | return loaded; 260 | } 261 | 262 | public static List loadClasspathFile(File file) throws IOException { 263 | ZipFile zipIn = new ZipFile(file); 264 | Enumeration entries = zipIn.entries(); 265 | boolean isJmod = file.getName().endsWith(".jmod"); 266 | 267 | List byteList = new ArrayList<>(zipIn.size()); 268 | 269 | while (entries.hasMoreElements()) { 270 | ZipEntry ent = entries.nextElement(); 271 | if (ent.getName().endsWith(".class") && (!isJmod || !ent.getName().endsWith("module-info.class") && ent.getName().startsWith("classes/"))) { 272 | byteList.add(readAllBytes(zipIn.getInputStream(ent))); 273 | } 274 | } 275 | zipIn.close(); 276 | 277 | return byteList; 278 | } 279 | 280 | public static byte[] readAllBytes(InputStream is) throws IOException { 281 | return readNBytes(is, Integer.MAX_VALUE); 282 | } 283 | 284 | public static byte[] readNBytes(InputStream is, int len) throws IOException { 285 | if (len < 0) { 286 | throw new IllegalArgumentException("len < 0"); 287 | } 288 | 289 | List bufs = null; 290 | byte[] result = null; 291 | int total = 0; 292 | int remaining = len; 293 | int n; 294 | do { 295 | byte[] buf = new byte[Math.min(remaining, 8192)]; 296 | int nread = 0; 297 | 298 | // read to EOF which may read more or less than buffer size 299 | while ((n = is.read(buf, nread, 300 | Math.min(buf.length - nread, remaining))) > 0) { 301 | nread += n; 302 | remaining -= n; 303 | } 304 | 305 | if (nread > 0) { 306 | if (Integer.MAX_VALUE - 8 - total < nread) { 307 | throw new OutOfMemoryError("Required array size too large"); 308 | } 309 | total += nread; 310 | if (result == null) { 311 | result = buf; 312 | } else { 313 | if (bufs == null) { 314 | bufs = new ArrayList<>(); 315 | bufs.add(result); 316 | } 317 | bufs.add(buf); 318 | } 319 | } 320 | // if the last call to read returned -1 or the number of bytes 321 | // requested have been read then break 322 | } while (n >= 0 && remaining > 0); 323 | 324 | if (bufs == null) { 325 | if (result == null) { 326 | return new byte[0]; 327 | } 328 | return result.length == total ? 329 | result : Arrays.copyOf(result, total); 330 | } 331 | 332 | result = new byte[total]; 333 | int offset = 0; 334 | remaining = total; 335 | for (byte[] b : bufs) { 336 | int count = Math.min(b.length, remaining); 337 | System.arraycopy(b, 0, result, offset, count); 338 | offset += count; 339 | remaining -= count; 340 | } 341 | 342 | return result; 343 | } 344 | 345 | } 346 | -------------------------------------------------------------------------------- /src/main/java/net/superblaubeere27/asmdelta/ASMDeltaPatch.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 superblaubeere27 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | * 6 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | */ 10 | 11 | package net.superblaubeere27.asmdelta; 12 | 13 | import net.superblaubeere27.asmdelta.difference.AbstractDifference; 14 | import net.superblaubeere27.asmdelta.utils.Utils; 15 | 16 | import java.io.IOException; 17 | import java.io.InputStream; 18 | import java.io.OutputStream; 19 | import java.nio.charset.StandardCharsets; 20 | import java.util.List; 21 | import java.util.zip.GZIPInputStream; 22 | import java.util.zip.GZIPOutputStream; 23 | 24 | import static net.superblaubeere27.asmdelta.ASMDelta.readAllBytes; 25 | 26 | public class ASMDeltaPatch { 27 | private String patchName; 28 | private int asmDeltaVersion; 29 | private List differenceList; 30 | 31 | public ASMDeltaPatch(String patchName, int asmDeltaVersion, List differenceList) { 32 | this.patchName = patchName; 33 | this.asmDeltaVersion = asmDeltaVersion; 34 | this.differenceList = differenceList; 35 | } 36 | 37 | public static ASMDeltaPatch read(InputStream inputStream) throws IOException { 38 | try (GZIPInputStream gz = new GZIPInputStream(inputStream)) { 39 | return Utils.GSON.fromJson(new String(readAllBytes(gz), StandardCharsets.UTF_8), ASMDeltaPatch.class); 40 | } 41 | } 42 | 43 | public String getPatchName() { 44 | return patchName; 45 | } 46 | 47 | public int getAsmDeltaVersion() { 48 | return asmDeltaVersion; 49 | } 50 | 51 | public List getDifferenceList() { 52 | return differenceList; 53 | } 54 | 55 | public void write(OutputStream outputStream) throws IOException { 56 | try (GZIPOutputStream gz = new GZIPOutputStream(outputStream)) { 57 | gz.write(Utils.GSON.toJson(this).getBytes(StandardCharsets.UTF_8)); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/net/superblaubeere27/asmdelta/ASMDeltaTransformer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2019 superblaubeere27 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package net.superblaubeere27.asmdelta; 26 | 27 | import btw.community.fabric.BTWFabricMod; 28 | import net.devtech.grossfabrichacks.transformer.asm.RawClassTransformer; 29 | import net.devtech.grossfabrichacks.unsafe.UnsafeUtil; 30 | import net.superblaubeere27.asmdelta.difference.AbstractDifference; 31 | import net.superblaubeere27.asmdelta.difference.clazz.AddClassDifference; 32 | import net.superblaubeere27.asmdelta.difference.clazz.ClassMakePublicDifference; 33 | import org.objectweb.asm.ClassReader; 34 | import org.objectweb.asm.ClassWriter; 35 | import org.objectweb.asm.tree.ClassNode; 36 | import org.objectweb.asm.tree.FieldNode; 37 | import org.objectweb.asm.tree.LocalVariableNode; 38 | import org.objectweb.asm.tree.MethodNode; 39 | import org.spongepowered.asm.transformers.MixinClassWriter; 40 | 41 | import java.io.File; 42 | import java.io.IOException; 43 | import java.lang.instrument.ClassFileTransformer; 44 | import java.lang.reflect.Field; 45 | import java.lang.reflect.InvocationTargetException; 46 | import java.lang.reflect.Method; 47 | import java.nio.file.Files; 48 | import java.nio.file.Path; 49 | import java.security.ProtectionDomain; 50 | import java.util.*; 51 | import java.util.regex.PatternSyntaxException; 52 | 53 | public class ASMDeltaTransformer implements ClassFileTransformer, RawClassTransformer { 54 | private static boolean EXPORT_CLASSES = false; 55 | private static boolean DEBUG = false; 56 | 57 | // Temporary hackaround 58 | private HashMap appliedPatches = new HashMap<>(); 59 | 60 | private HashMap> patches; 61 | 62 | public ASMDeltaTransformer(HashMap> patches) { 63 | this.patches = patches; 64 | // manually do a few patches 65 | patches.put("net.minecraft.src.SpawnerAnimals$1", new HashSet() {{ 66 | add(new ClassMakePublicDifference("net/minecraft/src/SpawnerAnimals$1")); 67 | }}); 68 | patches.put("net.minecraft.src.SpawnerAnimals$2", new HashSet() {{ 69 | add(new ClassMakePublicDifference("net/minecraft/src/SpawnerAnimals$2")); 70 | }}); 71 | 72 | patches.put("(?=(emi\\.|btw\\.|net\\.minecraft\\.))([\\w\\.]+\\$)([\\w\\.]+\\$?)*", new HashSet() {{ 73 | add(new ClassMakePublicDifference("", true)); 74 | }}); 75 | } 76 | 77 | @Override 78 | public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) { 79 | //println(className); 80 | // Check dot notation and slash notation. Use correct one. I dunno which one is correct, always mix them up 81 | if (className.contains(".")) { 82 | className = className.replace(".", "/"); 83 | } 84 | if (noMatchingPatches(className)) { 85 | className = className.replace("/", "."); 86 | if (noMatchingPatches(className)) { 87 | //println("Skipping class: " + className); 88 | return classfileBuffer; 89 | } 90 | } 91 | 92 | ClassNode classNode = null; 93 | if (classfileBuffer != null) { 94 | ClassReader classReader = new ClassReader(classfileBuffer); 95 | classNode = new ClassNode(); 96 | 97 | // The debug information is removed since it isn't needed and causes bugs (??) 98 | //classReader.accept(classNode, ClassReader.SKIP_DEBUG); 99 | classReader.accept(classNode, 0); 100 | } 101 | 102 | return doTransform(className, classNode); 103 | } 104 | 105 | private boolean noMatchingPatches(String className) { 106 | return !patches.containsKey(className) && patches.entrySet().parallelStream().noneMatch(x -> { 107 | try { 108 | return x.getValue().stream().anyMatch(y -> y.isRegex) && className.matches(x.getKey()); 109 | } catch (PatternSyntaxException e) { 110 | return false; 111 | } 112 | }); 113 | } 114 | 115 | public byte[] doTransform(String className, ClassNode classNode) { 116 | if (this.appliedPatches.containsKey(className)) { 117 | println("Error: Class " + className + " was already transformed!"); 118 | conjureNeededClasses(className); 119 | return this.appliedPatches.get(className); 120 | } 121 | classNode = applyPatches(className, classNode, classNode == null); 122 | 123 | // Recalculate frames and maximums. All classes are available at runtime 124 | // so it makes the agent a lot safer from producing illegal classes 125 | ClassWriter classWriter = new MixinClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); 126 | 127 | // set class version to Java 17 128 | classNode.version = 61; 129 | 130 | // This seems to work? 131 | for (MethodNode m : classNode.methods) { 132 | List toRemove = new ArrayList<>(); 133 | HashMap seen = new HashMap<>(); 134 | if (m == null || m.localVariables == null) continue; 135 | for (LocalVariableNode l : m.localVariables) { 136 | if (seen.containsKey(l.name)) { 137 | println("Duplicate local variable: " + l.name + " in " + className + "." + m.name + m.desc); 138 | println(" " + seen.get(l.name).desc + " vs " + l.desc); 139 | 140 | // Attempt to fix it 141 | if (seen.get(l.name).desc.equals(l.desc)) { 142 | println(" Fixing..."); 143 | // Remove the duplicate 144 | toRemove.add(l); 145 | } else { 146 | println(" Failed to fix"); 147 | } 148 | } else { 149 | seen.put(l.name, l); 150 | } 151 | } 152 | m.localVariables.removeAll(toRemove); 153 | } 154 | /*List toRemove = new ArrayList<>(); 155 | HashMap seen = new HashMap<>(); 156 | for (FieldNode f : classNode.fields) { 157 | if (f == null) continue; 158 | if (seen.containsKey(f.name)) { 159 | println("Duplicate field: " + f.name + " in " + className + "." + f.name + f.desc); 160 | println(" " + seen.get(f.name).desc + " vs " + f.desc); 161 | 162 | // Attempt to fix it 163 | if (seen.get(f.name).desc.equals(f.desc)) { 164 | println(" Fixing..."); 165 | // Remove the duplicate 166 | toRemove.add(f); 167 | } else { 168 | println(" Failed to fix"); 169 | } 170 | } else { 171 | seen.put(f.name, f); 172 | } 173 | } 174 | classNode.fields.removeAll(toRemove);*/ 175 | // Now check for inconsistent constant value types 176 | // Also seems to work? 177 | for (FieldNode f : classNode.fields) { 178 | if (f != null && f.value != null) { 179 | // Use the type of the Field as ground truth 180 | if (f.desc.equals("I")) { 181 | if (!(f.value instanceof Integer)) { 182 | println("Inconsistent constant value type: " + f.name + " in " + className); 183 | println(" " + f.value.getClass().getName() + " vs " + f.desc); 184 | println(" Fixing..."); 185 | f.value = ((Number) f.value).intValue(); 186 | } 187 | } else if (f.desc.equals("J")) { 188 | if (!(f.value instanceof Long)) { 189 | println("Inconsistent constant value type: " + f.name + " in " + className); 190 | println(" " + f.value.getClass().getName() + " vs " + f.desc); 191 | println(" Fixing..."); 192 | f.value = ((Number) f.value).longValue(); 193 | } 194 | } else if (f.desc.equals("F")) { 195 | if (!(f.value instanceof Float)) { 196 | println("Inconsistent constant value type: " + f.name + " in " + className); 197 | println(" " + f.value.getClass().getName() + " vs " + f.desc); 198 | println(" Fixing..."); 199 | f.value = ((Number) f.value).floatValue(); 200 | } 201 | } else if (f.desc.equals("D")) { 202 | if (!(f.value instanceof Double)) { 203 | println("Inconsistent constant value type: " + f.name + " in " + className); 204 | println(" " + f.value.getClass().getName() + " vs " + f.desc); 205 | println(" Fixing..."); 206 | f.value = ((Number) f.value).doubleValue(); 207 | } 208 | } else if (f.desc.equals("Z")) { 209 | if (!(f.value instanceof Boolean)) { 210 | println("Inconsistent constant value type: " + f.name + " in " + className); 211 | println(" " + f.value.getClass().getName() + " vs " + f.desc); 212 | println(" Fixing..."); 213 | f.value = ((Number) f.value).intValue() != 0; 214 | } 215 | } else if (f.desc.equals("S")) { 216 | if (!(f.value instanceof Short)) { 217 | println("Inconsistent constant value type: " + f.name + " in " + className); 218 | println(" " + f.value.getClass().getName() + " vs " + f.desc); 219 | println(" Fixing..."); 220 | f.value = ((Number) f.value).shortValue(); 221 | } 222 | } else if (f.desc.equals("B")) { 223 | if (!(f.value instanceof Byte)) { 224 | println("Inconsistent constant value type: " + f.name + " in " + className); 225 | println(" " + f.value.getClass().getName() + " vs " + f.desc); 226 | println(" Fixing..."); 227 | f.value = ((Number) f.value).byteValue(); 228 | } 229 | } else if (f.desc.equals("C")) { 230 | if (!(f.value instanceof Character)) { 231 | println("Inconsistent constant value type: " + f.name + " in " + className); 232 | println(" " + f.value.getClass().getName() + " vs " + f.desc); 233 | println(" Fixing..."); 234 | f.value = (char) ((Number) f.value).intValue(); 235 | } 236 | } 237 | } 238 | } 239 | 240 | classNode.accept(classWriter); 241 | byte[] b = classWriter.toByteArray(); 242 | // Export classes for debugging 243 | if (EXPORT_CLASSES) { 244 | try { 245 | Path exportPath = new File("./exported_classes/").toPath().resolve(className + ".class"); 246 | Files.createDirectories(exportPath.getParent()); 247 | Files.write(exportPath, b); 248 | } catch (IOException e) { 249 | e.printStackTrace(); 250 | } 251 | } 252 | conjureNeededClasses(className); 253 | 254 | this.appliedPatches.put(className, b); 255 | return b; 256 | } 257 | 258 | private void conjureNeededClasses(String className) { 259 | // Let me get my wand and conjure some classes 260 | // And yes, the dot notation is correct here 261 | if (className.equals(BTWFabricMod.getMappedName("net.minecraft.server.MinecraftServer"))) { 262 | conjureClass("net.minecraft.class_738"); 263 | } else if (className.equals(BTWFabricMod.getMappedName("net.minecraft.class_101"))) { 264 | conjureClass("net.minecraft.class_1444"); 265 | } 266 | } 267 | 268 | private List conjuredClasses = new ArrayList<>(); 269 | 270 | // This *can* fuck up the loading order 271 | private void conjureClass(String className) { 272 | if (conjuredClasses.contains(className)) return; 273 | ClassWriter classWriter; 274 | String name = BTWFabricMod.getMappedName(className); 275 | println("Conjuring class " + name); 276 | ClassNode classNode = new ClassNode(); 277 | classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS); 278 | classNode = this.doTransform2(name, classNode, true); 279 | if (classNode == null) { 280 | return; 281 | } 282 | classNode.accept(classWriter); 283 | // Apply Mixins to conjured classes like this: 284 | // BTWFabricMod.class.getClassLoader() is KnotClassLoader in this case 285 | // kcl.delegate.getMixinTransformer().transformClassBytes(String name, String transformedName, byte[] basicClass) 286 | // BUT everything is private... so, Reflection time! 287 | try { 288 | Object kcl = BTWFabricMod.class.getClassLoader(); // KnotClassLoader 289 | Field f = kcl.getClass().getDeclaredField("delegate"); 290 | f.setAccessible(true); 291 | Object delegate = f.get(kcl); // KnotClassDelegate 292 | f = delegate.getClass().getDeclaredField("mixinTransformer"); 293 | f.setAccessible(true); 294 | Object mixinTransformer = f.get(delegate); // FabricMixinTransformerProxy 295 | Method m = mixinTransformer.getClass().getDeclaredMethod("transformClassBytes", String.class, String.class, byte[].class); 296 | m.setAccessible(true); 297 | byte[] mixinOutput = (byte[]) m.invoke(mixinTransformer, name, name, classWriter.toByteArray()); 298 | UnsafeUtil.defineClass(name, mixinOutput); 299 | } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { 300 | e.printStackTrace(); 301 | } catch (NoSuchFieldException e) { 302 | throw new RuntimeException(e); 303 | } catch (Exception e) { 304 | println(e.getMessage()); 305 | } 306 | conjuredClasses.add(className); 307 | } 308 | 309 | public ClassNode doTransform2(String className, ClassNode classNode, boolean createNew) { 310 | println("Doing Transform2 " + className); 311 | if (this.appliedPatches.containsKey(className)) { 312 | println("Class " + className + " was already transformed!"); 313 | conjureNeededClasses(className); 314 | return classNode; 315 | } 316 | classNode = applyPatches(className, classNode, createNew); 317 | if (classNode == null) { 318 | return null; 319 | } 320 | // set class version to Java 17 321 | classNode.version = 61; 322 | 323 | ClassWriter classWriter = new MixinClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); 324 | classNode.accept(classWriter); 325 | 326 | byte[] b = classWriter.toByteArray(); 327 | this.appliedPatches.put(className, b); 328 | if (EXPORT_CLASSES) { 329 | try { 330 | Path exportPath = new File("./exported_classes/").toPath().resolve(className + ".class"); 331 | Files.createDirectories(exportPath.getParent()); 332 | Files.write(exportPath, b); 333 | } catch (IOException e) { 334 | e.printStackTrace(); 335 | } 336 | } 337 | 338 | conjureNeededClasses(className); 339 | return classNode; 340 | } 341 | 342 | private ClassNode applyPatches(String className, ClassNode classNode, boolean createNew) { 343 | println("Applying patches to " + className); 344 | 345 | HashMap map = new HashMap<>(); 346 | // The patch has to be applied to this one class only since they have already been grouped in AgentMain 347 | map.put(classNode != null ? classNode.name : className, classNode); 348 | 349 | // First apply regex patches 350 | for (Map.Entry> abstractDifferenceEntry : patches.entrySet().parallelStream().filter(x -> { 351 | try { 352 | return className.matches(x.getKey()); 353 | } catch (PatternSyntaxException e) { 354 | return false; 355 | } 356 | }).toList()) { 357 | for (AbstractDifference abstractDifference : abstractDifferenceEntry.getValue()) { 358 | if (!abstractDifference.isRegex) { 359 | continue; 360 | } 361 | try { 362 | println("Applying regex patch: " + abstractDifference.getClassName()); 363 | //println(abstractDifference.getClassName()); 364 | if (createNew && abstractDifference instanceof AddClassDifference) { 365 | //println("Creating new class"); 366 | abstractDifference.apply(map); 367 | } else if (!createNew && !(abstractDifference instanceof AddClassDifference)) { 368 | //println("Applying patch"); 369 | abstractDifference.apply(map); 370 | } 371 | } catch (Exception e) { 372 | //System.err.println("Failed to apply patch: " + e.getLocalizedMessage()); 373 | if (!(abstractDifference instanceof AddClassDifference)) { 374 | throw new IllegalStateException("Failed to apply patch", e); 375 | } else if (createNew && abstractDifference instanceof AddClassDifference) { 376 | throw new IllegalStateException("Failed to add class", e); 377 | } 378 | } 379 | } 380 | } 381 | 382 | for (AbstractDifference abstractDifference : patches.getOrDefault(className, new HashSet<>())) { 383 | if (abstractDifference.isRegex) { 384 | continue; 385 | } 386 | try { 387 | //println("Applying non-regex patch: " + abstractDifference.getClassName()); 388 | if (createNew && abstractDifference instanceof AddClassDifference) { 389 | //println("Creating new class"); 390 | abstractDifference.apply(map); 391 | } else if (!createNew && !(abstractDifference instanceof AddClassDifference)) { 392 | //println("Applying patch"); 393 | abstractDifference.apply(map); 394 | } 395 | } catch (Exception e) { 396 | //System.err.println("Failed to apply patch: " + e.getLocalizedMessage()); 397 | if (!(abstractDifference instanceof AddClassDifference)) { 398 | throw new IllegalStateException("Failed to apply patch", e); 399 | } else if (createNew && abstractDifference instanceof AddClassDifference) { 400 | throw new IllegalStateException("Failed to add class", e); 401 | } 402 | } 403 | } 404 | 405 | if (createNew) { 406 | return map.get(className.replace(".", "/")); 407 | } else { 408 | return classNode; 409 | } 410 | } 411 | 412 | private List inProgress = new ArrayList<>(); 413 | @Override 414 | public byte[] transform(String name, byte[] data) { 415 | if (inProgress.contains(name)) { 416 | return data; 417 | } 418 | inProgress.add(name); 419 | data = this.transform(null, name, null, null, data); 420 | inProgress.remove(name); 421 | return data; 422 | } 423 | 424 | private static void println(String s) { 425 | if (DEBUG) { 426 | System.out.println(s); 427 | } 428 | } 429 | } -------------------------------------------------------------------------------- /src/main/java/net/superblaubeere27/asmdelta/Main.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 superblaubeere27 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | * 6 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | */ 10 | 11 | package net.superblaubeere27.asmdelta; 12 | 13 | import joptsimple.*; 14 | import net.superblaubeere27.asmdelta.difference.AbstractDifference; 15 | import net.superblaubeere27.asmdelta.difference.VerificationException; 16 | import net.superblaubeere27.asmdelta.difference.clazz.AddClassDifference; 17 | import org.objectweb.asm.ClassWriter; 18 | 19 | import java.io.File; 20 | import java.io.FileOutputStream; 21 | import java.io.IOException; 22 | import java.util.List; 23 | import java.util.Set; 24 | import java.util.stream.Collectors; 25 | import java.util.zip.ZipEntry; 26 | import java.util.zip.ZipOutputStream; 27 | 28 | public class Main { 29 | private static final String VERSION = "ASMDelta v1.0.0"; 30 | 31 | 32 | public static void main(String[] args) throws IOException, VerificationException { 33 | OptionParser parser = new OptionParser(); 34 | 35 | ArgumentAcceptingOptionSpec jar1Option = parser.accepts("jar1", "The original file").withRequiredArg().ofType(File.class).required(); 36 | ArgumentAcceptingOptionSpec jar2Option = parser.accepts("jar2", "The new file").withRequiredArg().ofType(File.class).required(); 37 | 38 | ArgumentAcceptingOptionSpec patchFile = parser.accepts("o", "The generated patch location").withRequiredArg().ofType(File.class).required(); 39 | 40 | ArgumentAcceptingOptionSpec forJavaAgent = parser.accepts("forAgent", "Creates a patch for a java agent & dumps the additional classes needed at runtime.").withOptionalArg(); 41 | ArgumentAcceptingOptionSpec additionalClasses = parser.accepts("added-classes", "Where to output additional classes").requiredIf(forJavaAgent).withRequiredArg().ofType(File.class); 42 | 43 | ArgumentAcceptingOptionSpec patchName = parser.accepts("name", "The patch name").withOptionalArg().defaultsTo("N/A").ofType(String.class); 44 | 45 | OptionSpec help = parser.accepts("help", "Prints a help page").forHelp(); 46 | OptionSpec version = parser.accepts("version", "Prints the version on sysout").forHelp(); 47 | 48 | OptionSet parse; 49 | 50 | try { 51 | parse = parser.parse(args); 52 | } catch (OptionException e) { 53 | System.err.println(e.getMessage() + " (try --help)"); 54 | return; 55 | } 56 | 57 | if (parse.has(help)) { 58 | parser.printHelpOn(System.err); 59 | return; 60 | } 61 | if (parse.has(version)) { 62 | System.err.println(VERSION); 63 | return; 64 | } 65 | 66 | File jar1 = parse.valueOf(jar1Option); 67 | File jar2 = parse.valueOf(jar2Option); 68 | File outputFile = parse.valueOf(patchFile); 69 | 70 | if (jar1 == null || !jar1.exists()) { 71 | System.err.println("Jar 1 doesn't exist"); 72 | return; 73 | } 74 | if (jar2 == null || !jar2.exists()) { 75 | System.err.println("Jar 2 doesn't exist"); 76 | return; 77 | } 78 | 79 | System.out.println("Calculating delta..."); 80 | 81 | long l = System.currentTimeMillis(); 82 | 83 | List differences = ASMDelta.calculateDifference(1, 84 | jar1, 85 | jar2); 86 | 87 | System.out.println("Finished in " + (System.currentTimeMillis() - l) + "ms"); 88 | 89 | if (parse.has(forJavaAgent)) { 90 | Set illegalChanges = differences.stream().filter(e -> !e.canBeAppliedAtRuntime()).collect(Collectors.toSet()); 91 | 92 | if (illegalChanges.size() > 0) { 93 | System.err.println("There are " + illegalChanges.size() + " illegal patches (for java agent):"); 94 | 95 | for (AbstractDifference abstractDifference : illegalChanges) { 96 | System.err.println(abstractDifference); 97 | } 98 | } else { 99 | System.out.println("Patch is valid (for Java agent)."); 100 | } 101 | 102 | System.out.println("Writing added classes..."); 103 | 104 | Set newClasses = differences.stream().filter(e -> e instanceof AddClassDifference).map(e -> (AddClassDifference) e).collect(Collectors.toSet()); 105 | 106 | try (ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(parse.valueOf(additionalClasses)))) { 107 | for (AddClassDifference newClass : newClasses) { 108 | zipOutputStream.putNextEntry(new ZipEntry(newClass.getNewNode().name + ".class")); 109 | 110 | ClassWriter classWriter = new ClassWriter(0); 111 | 112 | newClass.getNewNode().accept(classWriter); 113 | 114 | zipOutputStream.write(classWriter.toByteArray()); 115 | 116 | zipOutputStream.closeEntry(); 117 | } 118 | } 119 | 120 | differences.removeAll(newClasses); 121 | 122 | } 123 | 124 | ASMDeltaPatch patch = new ASMDeltaPatch(parse.valueOf(patchName), 1, differences); 125 | 126 | outputFile.getParentFile().mkdirs(); 127 | 128 | try (FileOutputStream outputStream = new FileOutputStream(outputFile)) { 129 | patch.write(outputStream); 130 | } 131 | 132 | System.out.println("Wrote patch to " + outputFile.getAbsolutePath()); 133 | 134 | } 135 | 136 | } 137 | -------------------------------------------------------------------------------- /src/main/java/net/superblaubeere27/asmdelta/difference/AbstractDifference.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 superblaubeere27 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | * 6 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | */ 10 | 11 | package net.superblaubeere27.asmdelta.difference; 12 | 13 | import org.apache.commons.lang3.builder.ReflectionToStringBuilder; 14 | import org.objectweb.asm.tree.ClassNode; 15 | 16 | import java.util.HashMap; 17 | 18 | public abstract class AbstractDifference { 19 | public boolean isRegex = false; 20 | public abstract void apply(HashMap classes) throws VerificationException; 21 | 22 | public abstract boolean canBeAppliedAtRuntime(); 23 | 24 | public abstract String getClassName(); 25 | 26 | 27 | protected T verifyNotNull(T value, String errorMessage) throws VerificationException { 28 | if (value == null) { 29 | throw new VerificationException(errorMessage); 30 | } 31 | 32 | return value; 33 | } 34 | 35 | @Override 36 | public String toString() { 37 | return ReflectionToStringBuilder.toString(this); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/net/superblaubeere27/asmdelta/difference/VerificationException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 superblaubeere27 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | * 6 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | */ 10 | 11 | package net.superblaubeere27.asmdelta.difference; 12 | 13 | public class VerificationException extends Exception { 14 | public VerificationException() { 15 | } 16 | 17 | public VerificationException(String message) { 18 | super(message); 19 | } 20 | 21 | public VerificationException(String message, Throwable cause) { 22 | super(message, cause); 23 | } 24 | 25 | public VerificationException(Throwable cause) { 26 | super(cause); 27 | } 28 | 29 | public VerificationException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 30 | super(message, cause, enableSuppression, writableStackTrace); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/net/superblaubeere27/asmdelta/difference/clazz/AddClassDifference.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 superblaubeere27 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | * 6 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | */ 10 | 11 | package net.superblaubeere27.asmdelta.difference.clazz; 12 | 13 | import net.superblaubeere27.asmdelta.difference.AbstractDifference; 14 | import net.superblaubeere27.asmdelta.difference.VerificationException; 15 | import org.objectweb.asm.tree.ClassNode; 16 | 17 | import java.util.HashMap; 18 | 19 | public class AddClassDifference extends AbstractDifference { 20 | private ClassNode newNode; 21 | 22 | public AddClassDifference(ClassNode newNode) { 23 | this.newNode = newNode; 24 | } 25 | 26 | @Override 27 | public void apply(HashMap classes) throws VerificationException { 28 | if (classes.containsKey(newNode.name)) { 29 | throw new VerificationException(newNode.name + " already exists in the class pool"); 30 | } 31 | 32 | classes.put(newNode.name, newNode); 33 | } 34 | 35 | public ClassNode getNewNode() { 36 | return newNode; 37 | } 38 | 39 | @Override 40 | public String getClassName() { 41 | return newNode.name; 42 | } 43 | 44 | @Override 45 | public boolean canBeAppliedAtRuntime() { 46 | return true; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/net/superblaubeere27/asmdelta/difference/clazz/ClassAccessDifference.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 superblaubeere27 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | * 6 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | */ 10 | 11 | package net.superblaubeere27.asmdelta.difference.clazz; 12 | 13 | import net.superblaubeere27.asmdelta.difference.AbstractDifference; 14 | import org.objectweb.asm.Opcodes; 15 | import org.objectweb.asm.tree.ClassNode; 16 | 17 | import java.util.HashMap; 18 | 19 | public class ClassAccessDifference extends AbstractDifference { 20 | private String className; 21 | private int oldAccess; 22 | private int newAccess; 23 | 24 | public ClassAccessDifference(String className, int oldAccess, int newAccess) { 25 | this.className = className; 26 | this.oldAccess = oldAccess; 27 | this.newAccess = newAccess; 28 | } 29 | 30 | @Override 31 | public void apply(HashMap classes) { 32 | classes.get(className).access = newAccess; 33 | } 34 | 35 | @Override 36 | public String getClassName() { 37 | return className; 38 | } 39 | 40 | @Override 41 | public boolean canBeAppliedAtRuntime() { 42 | return (oldAccess & Opcodes.ACC_INTERFACE) != (newAccess & Opcodes.ACC_INTERFACE) // We can't change a class type on the fly 43 | || (oldAccess & Opcodes.ACC_ENUM) != (newAccess & Opcodes.ACC_ENUM); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/net/superblaubeere27/asmdelta/difference/clazz/ClassMakePublicDifference.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 superblaubeere27 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | * 6 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | */ 10 | 11 | package net.superblaubeere27.asmdelta.difference.clazz; 12 | 13 | import net.superblaubeere27.asmdelta.difference.AbstractDifference; 14 | import org.objectweb.asm.tree.ClassNode; 15 | import org.objectweb.asm.tree.FieldNode; 16 | import org.objectweb.asm.tree.MethodNode; 17 | 18 | import java.util.HashMap; 19 | 20 | import static org.objectweb.asm.Opcodes.*; 21 | 22 | 23 | public class ClassMakePublicDifference extends AbstractDifference { 24 | private String className; 25 | public ClassMakePublicDifference(String className) { 26 | this.className = className; 27 | } 28 | 29 | public ClassMakePublicDifference(String className, boolean isRegex) { 30 | this.className = className; 31 | this.isRegex = isRegex; 32 | } 33 | 34 | public static ClassMakePublicDifference createNew(String className) { 35 | return new ClassMakePublicDifference(className); 36 | } 37 | 38 | @Override 39 | public void apply(HashMap classes) { 40 | if (isRegex) { 41 | for (String s : classes.keySet()) { 42 | if (s.matches(className)) { 43 | makePublic(classes, s); 44 | } 45 | } 46 | } else { 47 | makePublic(classes, className); 48 | } 49 | } 50 | 51 | private void makePublic(HashMap classes, String className) { 52 | System.out.println("Making " + className + " public"); 53 | ClassNode node = classes.get(className); 54 | node.access = (node.access & ~(ACC_PRIVATE | ACC_PROTECTED)) | (ACC_PUBLIC | ACC_SUPER); 55 | for (MethodNode method : node.methods) { 56 | method.access = (method.access & ~(ACC_PRIVATE | ACC_PROTECTED)) | ACC_PUBLIC; 57 | } 58 | for (FieldNode field : node.fields) { 59 | field.access = (field.access & ~(ACC_PRIVATE | ACC_PROTECTED)) | ACC_PUBLIC; 60 | } 61 | } 62 | 63 | 64 | @Override 65 | public String getClassName() { 66 | return className; 67 | } 68 | 69 | @Override 70 | public boolean canBeAppliedAtRuntime() { 71 | return true; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/net/superblaubeere27/asmdelta/difference/clazz/ClassMetadataDifference.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 superblaubeere27 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | * 6 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | */ 10 | 11 | package net.superblaubeere27.asmdelta.difference.clazz; 12 | 13 | import net.superblaubeere27.asmdelta.difference.AbstractDifference; 14 | import org.objectweb.asm.tree.ClassNode; 15 | 16 | import java.util.HashMap; 17 | import java.util.List; 18 | 19 | //import static jdk.internal.org.objectweb.asm.Opcodes.ACC_SUPER; 20 | 21 | public class ClassMetadataDifference extends AbstractDifference { 22 | private String className; 23 | 24 | private String outerClass; 25 | private String nestHostClass; 26 | private String outerMethod; 27 | private String outerMethodDesc; 28 | private String signature; 29 | private String sourceDebug; 30 | private String sourceFile; 31 | private String superName; 32 | private List interfaces; 33 | 34 | public ClassMetadataDifference(String className, String outerClass, String nestHostClass, String outerMethod, String outerMethodDesc, String signature, String sourceDebug, String sourceFile, String superName, List interfaces) { 35 | this.className = className; 36 | this.outerClass = outerClass; 37 | this.nestHostClass = nestHostClass; 38 | this.outerMethod = outerMethod; 39 | this.outerMethodDesc = outerMethodDesc; 40 | this.signature = signature; 41 | this.sourceDebug = sourceDebug; 42 | this.sourceFile = sourceFile; 43 | this.superName = superName; 44 | this.interfaces = interfaces; 45 | } 46 | 47 | public static ClassMetadataDifference createNew(String className, String outerClass, String nestHostClass, String outerMethod, String outerMethodDesc, String signature, String sourceDebug, String sourceFile, String superName, List interfaces) { 48 | if (outerClass == null 49 | && nestHostClass == null 50 | && outerMethod == null 51 | && outerMethodDesc == null 52 | && signature == null 53 | && sourceDebug == null 54 | && sourceFile == null 55 | && superName == null 56 | && interfaces == null) { 57 | return null; 58 | } 59 | 60 | 61 | return new ClassMetadataDifference(className, outerClass, nestHostClass, outerMethod, outerMethodDesc, signature, sourceDebug, sourceFile, superName, interfaces); 62 | } 63 | 64 | 65 | @Override 66 | public void apply(HashMap classes) { 67 | ClassNode node = classes.get(className); 68 | 69 | if (outerClass != null) 70 | node.outerClass = outerClass; 71 | if (nestHostClass != null) 72 | node.nestHostClass = nestHostClass; 73 | if (outerMethod != null) 74 | node.outerMethod = outerMethod; 75 | if (outerMethodDesc != null) 76 | node.outerMethodDesc = outerMethodDesc; 77 | if (signature != null) 78 | node.signature = signature; 79 | if (sourceDebug != null) 80 | node.sourceDebug = sourceDebug; 81 | if (sourceFile != null) 82 | node.sourceFile = sourceFile; 83 | if (superName != null) 84 | node.superName = superName; 85 | if (interfaces != null) 86 | node.interfaces = interfaces; 87 | } 88 | 89 | @Override 90 | public String getClassName() { 91 | return className; 92 | } 93 | 94 | @Override 95 | public boolean canBeAppliedAtRuntime() { 96 | return superName != null; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/net/superblaubeere27/asmdelta/difference/clazz/ClassVersionDifference.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 superblaubeere27 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | * 6 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | */ 10 | 11 | package net.superblaubeere27.asmdelta.difference.clazz; 12 | 13 | import net.superblaubeere27.asmdelta.difference.AbstractDifference; 14 | import org.objectweb.asm.tree.ClassNode; 15 | 16 | import java.util.HashMap; 17 | 18 | public class ClassVersionDifference extends AbstractDifference { 19 | private String className; 20 | private int newVersion; 21 | 22 | public ClassVersionDifference(String className, int newVersion) { 23 | this.className = className; 24 | this.newVersion = newVersion; 25 | } 26 | 27 | @Override 28 | public void apply(HashMap classes) { 29 | classes.get(className).version = newVersion; 30 | } 31 | 32 | @Override 33 | public String getClassName() { 34 | return className; 35 | } 36 | 37 | @Override 38 | public boolean canBeAppliedAtRuntime() { 39 | return true; // TODO Can a version change be applied at runtime? (Assuming yes) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/net/superblaubeere27/asmdelta/difference/clazz/RemoveClassDifference.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 superblaubeere27 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | * 6 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | */ 10 | 11 | package net.superblaubeere27.asmdelta.difference.clazz; 12 | 13 | import net.superblaubeere27.asmdelta.difference.AbstractDifference; 14 | import net.superblaubeere27.asmdelta.difference.VerificationException; 15 | import org.objectweb.asm.tree.ClassNode; 16 | 17 | import java.util.HashMap; 18 | 19 | public class RemoveClassDifference extends AbstractDifference { 20 | private String className; 21 | 22 | public RemoveClassDifference(String className) { 23 | this.className = className; 24 | } 25 | 26 | @Override 27 | public void apply(HashMap classes) throws VerificationException { 28 | if (!classes.containsKey(className)) { 29 | throw new VerificationException(className + " isn't in the class pool"); 30 | } 31 | 32 | classes.remove(className); 33 | } 34 | 35 | @Override 36 | public String getClassName() { 37 | return className; 38 | } 39 | 40 | @Override 41 | public boolean canBeAppliedAtRuntime() { 42 | return true; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/net/superblaubeere27/asmdelta/difference/fields/AddFieldDifference.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 superblaubeere27 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | * 6 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | */ 10 | 11 | package net.superblaubeere27.asmdelta.difference.fields; 12 | 13 | import net.superblaubeere27.asmdelta.difference.AbstractDifference; 14 | import org.objectweb.asm.tree.ClassNode; 15 | import org.objectweb.asm.tree.FieldNode; 16 | 17 | import java.util.ArrayList; 18 | import java.util.HashMap; 19 | import java.util.List; 20 | 21 | public class AddFieldDifference extends AbstractDifference { 22 | private String className; 23 | private FieldNode fieldNode; 24 | 25 | public AddFieldDifference(String className, FieldNode fieldNode) { 26 | this.className = className; 27 | this.fieldNode = fieldNode; 28 | } 29 | 30 | @Override 31 | public void apply(HashMap classes) { 32 | ClassNode classNode = classes.get(className); 33 | 34 | List fields = classNode.fields; 35 | 36 | if (fields == null) { // According to the documentation, fields might be null 37 | classNode.fields = fields = new ArrayList<>(); 38 | } 39 | 40 | fields.add(fieldNode); 41 | 42 | } 43 | 44 | @Override 45 | public String getClassName() { 46 | return className; 47 | } 48 | 49 | @Override 50 | public boolean canBeAppliedAtRuntime() { 51 | return false; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/net/superblaubeere27/asmdelta/difference/fields/FieldAccessDifference.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 superblaubeere27 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | * 6 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | */ 10 | 11 | package net.superblaubeere27.asmdelta.difference.fields; 12 | 13 | import net.superblaubeere27.asmdelta.difference.AbstractDifference; 14 | import net.superblaubeere27.asmdelta.difference.VerificationException; 15 | import org.objectweb.asm.tree.ClassNode; 16 | import org.objectweb.asm.tree.FieldNode; 17 | 18 | import java.util.HashMap; 19 | 20 | public class FieldAccessDifference extends AbstractDifference { 21 | private String className; 22 | private String fieldName; 23 | private int oldAccess; 24 | private int newAccess; 25 | 26 | public FieldAccessDifference(String className, String fieldName, int oldAccess, int newAccess) { 27 | this.className = className; 28 | this.fieldName = fieldName; 29 | this.oldAccess = oldAccess; 30 | this.newAccess = newAccess; 31 | } 32 | 33 | @Override 34 | public void apply(HashMap classes) throws VerificationException { 35 | FieldNode fieldNode = verifyNotNull(verifyNotNull(classes.get(className), "Class wasn't found").fields, "Field wasn't found") 36 | .stream() 37 | .filter(f -> f.name.equals(fieldName)) 38 | .findFirst() 39 | .orElseThrow(VerificationException::new); 40 | 41 | fieldNode.access = newAccess; 42 | } 43 | 44 | @Override 45 | public boolean canBeAppliedAtRuntime() { 46 | return false; 47 | } 48 | 49 | @Override 50 | public String getClassName() { 51 | return className; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/net/superblaubeere27/asmdelta/difference/fields/FieldDescriptionDifference.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 superblaubeere27 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | * 6 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | */ 10 | 11 | package net.superblaubeere27.asmdelta.difference.fields; 12 | 13 | import net.superblaubeere27.asmdelta.difference.AbstractDifference; 14 | import net.superblaubeere27.asmdelta.difference.VerificationException; 15 | import org.objectweb.asm.tree.ClassNode; 16 | import org.objectweb.asm.tree.FieldNode; 17 | 18 | import java.util.HashMap; 19 | 20 | public class FieldDescriptionDifference extends AbstractDifference { 21 | private String className; 22 | private String fieldName; 23 | private String desc; 24 | 25 | public FieldDescriptionDifference(String className, String fieldName, String desc) { 26 | this.className = className; 27 | this.fieldName = fieldName; 28 | this.desc = desc; 29 | } 30 | 31 | @Override 32 | public void apply(HashMap classes) throws VerificationException { 33 | FieldNode fieldNode = verifyNotNull(verifyNotNull(classes.get(className), "Class wasn't found").fields, "Field wasn't found") 34 | .stream() 35 | .filter(f -> f.name.equals(fieldName)) 36 | .findFirst() 37 | .orElseThrow(VerificationException::new); 38 | 39 | fieldNode.desc = desc; 40 | } 41 | 42 | @Override 43 | public String getClassName() { 44 | return className; 45 | } 46 | 47 | @Override 48 | public boolean canBeAppliedAtRuntime() { 49 | return true; // TODO Can changing a field's type at runtime be a problem? (Assuming yes) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/net/superblaubeere27/asmdelta/difference/fields/FieldSignatureDifference.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 superblaubeere27 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | * 6 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | */ 10 | 11 | package net.superblaubeere27.asmdelta.difference.fields; 12 | 13 | import net.superblaubeere27.asmdelta.difference.AbstractDifference; 14 | import net.superblaubeere27.asmdelta.difference.VerificationException; 15 | import org.objectweb.asm.tree.ClassNode; 16 | import org.objectweb.asm.tree.FieldNode; 17 | 18 | import java.util.HashMap; 19 | 20 | public class FieldSignatureDifference extends AbstractDifference { 21 | private String className; 22 | private String fieldName; 23 | private String signature; 24 | 25 | public FieldSignatureDifference(String className, String fieldName, String signature) { 26 | this.className = className; 27 | this.fieldName = fieldName; 28 | this.signature = signature; 29 | } 30 | 31 | @Override 32 | public void apply(HashMap classes) throws VerificationException { 33 | FieldNode fieldNode = verifyNotNull(verifyNotNull(classes.get(className), "Class wasn't found").fields, "Field wasn't found") 34 | .stream() 35 | .filter(f -> f.name.equals(fieldName)) 36 | .findFirst() 37 | .orElseThrow(VerificationException::new); 38 | 39 | fieldNode.signature = signature; 40 | } 41 | 42 | @Override 43 | public String getClassName() { 44 | return className; 45 | } 46 | 47 | @Override 48 | public boolean canBeAppliedAtRuntime() { 49 | return true; // TODO Can changing a field's signature at runtime be a problem? (Assuming no) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/net/superblaubeere27/asmdelta/difference/fields/FieldValueDifference.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 superblaubeere27 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | * 6 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | */ 10 | 11 | package net.superblaubeere27.asmdelta.difference.fields; 12 | 13 | import net.superblaubeere27.asmdelta.difference.AbstractDifference; 14 | import net.superblaubeere27.asmdelta.difference.VerificationException; 15 | import org.objectweb.asm.tree.ClassNode; 16 | import org.objectweb.asm.tree.FieldNode; 17 | 18 | import java.util.HashMap; 19 | 20 | public class FieldValueDifference extends AbstractDifference { 21 | private String className; 22 | private String fieldName; 23 | private Object value; 24 | 25 | public FieldValueDifference(String className, String fieldName, Object value) { 26 | this.className = className; 27 | this.fieldName = fieldName; 28 | this.value = value; 29 | } 30 | 31 | @Override 32 | public void apply(HashMap classes) throws VerificationException { 33 | FieldNode fieldNode = verifyNotNull(verifyNotNull(classes.get(className), "Class wasn't found").fields, "Field wasn't found") 34 | .stream() 35 | .filter(f -> f.name.equals(fieldName)) 36 | .findFirst() 37 | .orElseThrow(VerificationException::new); 38 | 39 | fieldNode.value = value; 40 | } 41 | 42 | @Override 43 | public String getClassName() { 44 | return className; 45 | } 46 | 47 | @Override 48 | public boolean canBeAppliedAtRuntime() { 49 | return false; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/net/superblaubeere27/asmdelta/difference/fields/RemoveFieldDifference.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 superblaubeere27 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | * 6 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | */ 10 | 11 | package net.superblaubeere27.asmdelta.difference.fields; 12 | 13 | import net.superblaubeere27.asmdelta.difference.AbstractDifference; 14 | import net.superblaubeere27.asmdelta.difference.VerificationException; 15 | import org.objectweb.asm.tree.ClassNode; 16 | import org.objectweb.asm.tree.FieldNode; 17 | 18 | import java.util.HashMap; 19 | import java.util.List; 20 | import java.util.stream.Collectors; 21 | 22 | public class RemoveFieldDifference extends AbstractDifference { 23 | private String className; 24 | private String fieldName; 25 | 26 | public RemoveFieldDifference(String className, String fieldName) { 27 | this.className = className; 28 | this.fieldName = fieldName; 29 | } 30 | 31 | @Override 32 | public void apply(HashMap classes) throws VerificationException { 33 | ClassNode classNode = classes.get(className); 34 | 35 | List fields = classNode.fields; 36 | 37 | if (fields == null) { // According to the documentation, fields might be null 38 | throw new VerificationException("Class has no fields to remove"); 39 | } 40 | 41 | fields.removeAll(fields.stream().filter(field -> field.name.equals(fieldName)).collect(Collectors.toList())); 42 | } 43 | 44 | @Override 45 | public String getClassName() { 46 | return className; 47 | } 48 | 49 | @Override 50 | public boolean canBeAppliedAtRuntime() { 51 | return false; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/net/superblaubeere27/asmdelta/difference/methods/AddMethodDifference.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 superblaubeere27 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | * 6 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | */ 10 | 11 | package net.superblaubeere27.asmdelta.difference.methods; 12 | 13 | import net.fabricmc.api.EnvType; 14 | import net.fabricmc.loader.impl.launch.knot.Knot; 15 | import net.superblaubeere27.asmdelta.difference.AbstractDifference; 16 | import org.objectweb.asm.tree.ClassNode; 17 | import org.objectweb.asm.tree.MethodNode; 18 | 19 | import java.util.ArrayList; 20 | import java.util.HashMap; 21 | import java.util.List; 22 | 23 | public class AddMethodDifference extends AbstractDifference { 24 | private String className; 25 | private MethodNode methodNode; 26 | 27 | public AddMethodDifference(String className, MethodNode methodNode) { 28 | this.className = className; 29 | this.methodNode = methodNode; 30 | } 31 | 32 | @Override 33 | public void apply(HashMap classes) { 34 | ClassNode classNode = classes.get(className); 35 | 36 | List methods = classNode.methods; 37 | 38 | if (methods == null) { // According to the documentation, methods might be null 39 | classNode.methods = methods = new ArrayList<>(); 40 | } 41 | 42 | // Check if the method already exists 43 | MethodNode existingMethod = null; 44 | for (MethodNode method : methods) { 45 | if (method.name.equals(methodNode.name) && method.desc.equals(methodNode.desc)) { 46 | existingMethod = method; 47 | break; 48 | } 49 | } 50 | // Yeet the old method, who needs it anyway 51 | methods.remove(existingMethod); 52 | 53 | if (methodNode.invisibleAnnotations != null && !methodNode.invisibleAnnotations.isEmpty()) { 54 | for (int i = 0; i < methodNode.invisibleAnnotations.size(); i++) { 55 | if (methodNode.invisibleAnnotations.get(i).desc.equals("Lnet/fabricmc/api/Environment;") && ((String[]) methodNode.invisibleAnnotations.get(i).values.get(1))[1].equals("CLIENT")) { 56 | if (Knot.getLauncher().getEnvironmentType() == EnvType.SERVER) { 57 | return; 58 | } 59 | } 60 | } 61 | } 62 | 63 | methods.add(methodNode); 64 | 65 | } 66 | 67 | @Override 68 | public String getClassName() { 69 | return className; 70 | } 71 | 72 | @Override 73 | public boolean canBeAppliedAtRuntime() { 74 | return false; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/net/superblaubeere27/asmdelta/difference/methods/MethodAccessDifference.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 superblaubeere27 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | * 6 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | */ 10 | 11 | package net.superblaubeere27.asmdelta.difference.methods; 12 | 13 | import net.superblaubeere27.asmdelta.difference.AbstractDifference; 14 | import net.superblaubeere27.asmdelta.difference.VerificationException; 15 | import org.objectweb.asm.tree.ClassNode; 16 | import org.objectweb.asm.tree.MethodNode; 17 | 18 | import java.util.HashMap; 19 | 20 | public class MethodAccessDifference extends AbstractDifference { 21 | private String className; 22 | private String methodName; 23 | private String methodDesc; 24 | private int oldAccess; 25 | private int newAccess; 26 | 27 | public MethodAccessDifference(String className, String methodName, String methodDesc, int oldAccess, int newAccess) { 28 | this.className = className; 29 | this.methodName = methodName; 30 | this.methodDesc = methodDesc; 31 | this.oldAccess = oldAccess; 32 | this.newAccess = newAccess; 33 | } 34 | 35 | @Override 36 | public void apply(HashMap classes) throws VerificationException { 37 | MethodNode methodNode = verifyNotNull(verifyNotNull(classes.get(className), "Class wasn't found").methods, "Field wasn't found") 38 | .stream() 39 | .filter(m -> m.name.equals(methodName) && m.desc.equals(methodDesc)) 40 | .findFirst() 41 | .orElseThrow(VerificationException::new); 42 | 43 | methodNode.access = newAccess; 44 | } 45 | 46 | @Override 47 | public String getClassName() { 48 | return className; 49 | } 50 | 51 | @Override 52 | public boolean canBeAppliedAtRuntime() { 53 | return false; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/net/superblaubeere27/asmdelta/difference/methods/MethodAnnotationDefaultDifference.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 superblaubeere27 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | * 6 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | */ 10 | 11 | package net.superblaubeere27.asmdelta.difference.methods; 12 | 13 | import net.superblaubeere27.asmdelta.difference.AbstractDifference; 14 | import net.superblaubeere27.asmdelta.difference.VerificationException; 15 | import org.objectweb.asm.tree.ClassNode; 16 | import org.objectweb.asm.tree.MethodNode; 17 | 18 | import java.util.HashMap; 19 | 20 | public class MethodAnnotationDefaultDifference extends AbstractDifference { 21 | private String className; 22 | private String methodName; 23 | private String methodDesc; 24 | private Object newDefault; 25 | 26 | public MethodAnnotationDefaultDifference(String className, String methodName, String methodDesc, Object newDefault) { 27 | this.className = className; 28 | this.methodName = methodName; 29 | this.methodDesc = methodDesc; 30 | this.newDefault = newDefault; 31 | } 32 | 33 | @Override 34 | public void apply(HashMap classes) throws VerificationException { 35 | MethodNode methodNode = verifyNotNull(verifyNotNull(classes.get(className), "Class wasn't found").methods, "Field wasn't found") 36 | .stream() 37 | .filter(m -> m.name.equals(methodName) && m.desc.equals(methodDesc)) 38 | .findFirst() 39 | .orElseThrow(VerificationException::new); 40 | 41 | methodNode.annotationDefault = newDefault; 42 | } 43 | 44 | @Override 45 | public String getClassName() { 46 | return className; 47 | } 48 | 49 | @Override 50 | public boolean canBeAppliedAtRuntime() { 51 | return true; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/net/superblaubeere27/asmdelta/difference/methods/MethodExceptionDifference.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 superblaubeere27 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | * 6 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | */ 10 | 11 | package net.superblaubeere27.asmdelta.difference.methods; 12 | 13 | import net.superblaubeere27.asmdelta.difference.AbstractDifference; 14 | import net.superblaubeere27.asmdelta.difference.VerificationException; 15 | import org.objectweb.asm.tree.ClassNode; 16 | import org.objectweb.asm.tree.MethodNode; 17 | 18 | import java.util.HashMap; 19 | import java.util.List; 20 | 21 | public class MethodExceptionDifference extends AbstractDifference { 22 | private String className; 23 | private String methodName; 24 | private String methodDesc; 25 | private List newExceptions; 26 | 27 | public MethodExceptionDifference(String className, String methodName, String methodDesc, List newExceptions) { 28 | this.className = className; 29 | this.methodName = methodName; 30 | this.methodDesc = methodDesc; 31 | this.newExceptions = newExceptions; 32 | } 33 | 34 | @Override 35 | public void apply(HashMap classes) throws VerificationException { 36 | MethodNode methodNode = verifyNotNull(verifyNotNull(classes.get(className), "Class wasn't found").methods, "Field wasn't found") 37 | .stream() 38 | .filter(m -> m.name.equals(methodName) && m.desc.equals(methodDesc)) 39 | .findFirst() 40 | .orElseThrow(VerificationException::new); 41 | 42 | methodNode.exceptions = newExceptions; 43 | } 44 | 45 | @Override 46 | public String getClassName() { 47 | return className; 48 | } 49 | 50 | @Override 51 | public boolean canBeAppliedAtRuntime() { 52 | return true; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/net/superblaubeere27/asmdelta/difference/methods/MethodInstructionDifference.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 superblaubeere27 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | * 6 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | */ 10 | 11 | package net.superblaubeere27.asmdelta.difference.methods; 12 | 13 | import net.superblaubeere27.asmdelta.difference.AbstractDifference; 14 | import net.superblaubeere27.asmdelta.difference.VerificationException; 15 | import org.objectweb.asm.Label; 16 | import org.objectweb.asm.tree.*; 17 | import org.objectweb.asm.util.Textifier; 18 | 19 | import java.util.Arrays; 20 | import java.util.HashMap; 21 | 22 | public class MethodInstructionDifference extends AbstractDifference { 23 | private String className; 24 | private String methodName; 25 | private String methodDesc; 26 | private MethodNode content; 27 | 28 | public MethodInstructionDifference(String className, String methodName, String methodDesc, MethodNode methodNode) { 29 | this.className = className; 30 | this.methodName = methodName; 31 | this.methodDesc = methodDesc; 32 | //this.content = new MethodNode(); 33 | 34 | this.content = methodNode; 35 | } 36 | 37 | @Override 38 | public void apply(HashMap classes) throws VerificationException { 39 | try { 40 | /*if (classes.keySet().isEmpty()) { 41 | System.out.println("Classes is empty"); 42 | } else { 43 | System.out.println(Arrays.toString(classes.keySet().toArray())); 44 | }*/ 45 | MethodNode methodNode = verifyNotNull(verifyNotNull(classes.get(className), className + " class wasn't found").methods, "Field wasn't found") 46 | .stream() 47 | .filter(m -> m.name.equals(methodName) && m.desc.equals(methodDesc)) 48 | .findFirst() 49 | .orElseThrow(VerificationException::new); 50 | methodNode.instructions.clear(); 51 | methodNode.instructions = new InsnList(); 52 | AbstractInsnNode[] abstractInsnNodes = this.content.instructions.toArray(); 53 | for (AbstractInsnNode abstractInsnNode : abstractInsnNodes) { 54 | methodNode.instructions.add(abstractInsnNode); 55 | } 56 | //this.content.instructions.clear(); 57 | methodNode.tryCatchBlocks = this.content.tryCatchBlocks; 58 | } catch (VerificationException e) { 59 | System.out.println("Method " + methodName + " not found"); 60 | throw e; 61 | } catch (Exception e) { 62 | System.err.println("Error: " + e.getMessage()); 63 | //throw e; 64 | } 65 | } 66 | 67 | @Override 68 | public String getClassName() { 69 | return className; 70 | } 71 | 72 | @Override 73 | public boolean canBeAppliedAtRuntime() { 74 | return true; 75 | } 76 | 77 | public String getMethodName() { 78 | return methodName; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/net/superblaubeere27/asmdelta/difference/methods/MethodLocalVariableDifference.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 superblaubeere27 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | * 6 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | */ 10 | 11 | package net.superblaubeere27.asmdelta.difference.methods; 12 | 13 | import net.superblaubeere27.asmdelta.difference.AbstractDifference; 14 | import net.superblaubeere27.asmdelta.difference.VerificationException; 15 | import org.objectweb.asm.tree.ClassNode; 16 | import org.objectweb.asm.tree.LocalVariableNode; 17 | import org.objectweb.asm.tree.MethodNode; 18 | 19 | import java.util.HashMap; 20 | import java.util.List; 21 | 22 | public class MethodLocalVariableDifference extends AbstractDifference { 23 | private String className; 24 | public String methodName; 25 | public String methodDesc; 26 | public List localVariables; 27 | 28 | public MethodLocalVariableDifference(String className, String methodName, String methodDesc, List localVariables) { 29 | this.className = className; 30 | this.methodName = methodName; 31 | this.methodDesc = methodDesc; 32 | this.localVariables = localVariables; 33 | } 34 | 35 | @Override 36 | public void apply(HashMap classes) throws VerificationException { 37 | MethodNode methodNode = verifyNotNull(verifyNotNull(classes.get(className), "Class wasn't found").methods, "Method wasn't found") 38 | .stream() 39 | .filter(m -> m.name.equals(methodName) && m.desc.equals(methodDesc)) 40 | .findFirst() 41 | .orElseThrow(VerificationException::new); 42 | 43 | methodNode.localVariables = localVariables; 44 | } 45 | 46 | @Override 47 | public boolean canBeAppliedAtRuntime() { 48 | return true; 49 | } 50 | 51 | @Override 52 | public String getClassName() { 53 | return className; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/net/superblaubeere27/asmdelta/difference/methods/MethodMaxsDifference.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 superblaubeere27 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | * 6 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | */ 10 | 11 | package net.superblaubeere27.asmdelta.difference.methods; 12 | 13 | import net.superblaubeere27.asmdelta.difference.AbstractDifference; 14 | import net.superblaubeere27.asmdelta.difference.VerificationException; 15 | import org.objectweb.asm.tree.ClassNode; 16 | import org.objectweb.asm.tree.MethodNode; 17 | 18 | import java.util.HashMap; 19 | 20 | public class MethodMaxsDifference extends AbstractDifference { 21 | private String className; 22 | private String methodName; 23 | private String methodDesc; 24 | private int maxStack; 25 | private int maxLocals; 26 | 27 | public MethodMaxsDifference(String className, String methodName, String methodDesc, int maxStack, int maxLocals) { 28 | this.className = className; 29 | this.methodName = methodName; 30 | this.methodDesc = methodDesc; 31 | this.maxStack = maxStack; 32 | this.maxLocals = maxLocals; 33 | } 34 | 35 | @Override 36 | public void apply(HashMap classes) throws VerificationException { 37 | MethodNode methodNode = verifyNotNull(verifyNotNull(classes.get(className), "Class wasn't found").methods, "Field wasn't found") 38 | .stream() 39 | .filter(m -> m.name.equals(methodName) && m.desc.equals(methodDesc)) 40 | .findFirst() 41 | .orElseThrow(VerificationException::new); 42 | 43 | methodNode.maxStack = maxStack; 44 | methodNode.maxLocals = maxLocals; 45 | } 46 | 47 | @Override 48 | public String getClassName() { 49 | return className; 50 | } 51 | 52 | @Override 53 | public boolean canBeAppliedAtRuntime() { 54 | return true; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/net/superblaubeere27/asmdelta/difference/methods/MethodSignatureDifference.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 superblaubeere27 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | * 6 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | */ 10 | 11 | package net.superblaubeere27.asmdelta.difference.methods; 12 | 13 | import net.superblaubeere27.asmdelta.difference.AbstractDifference; 14 | import net.superblaubeere27.asmdelta.difference.VerificationException; 15 | import org.objectweb.asm.tree.ClassNode; 16 | import org.objectweb.asm.tree.MethodNode; 17 | 18 | import java.util.HashMap; 19 | 20 | public class MethodSignatureDifference extends AbstractDifference { 21 | private String className; 22 | private String methodName; 23 | private String methodDesc; 24 | private String newSignature; 25 | 26 | public MethodSignatureDifference(String className, String methodName, String methodDesc, String newSignature) { 27 | this.className = className; 28 | this.methodName = methodName; 29 | this.methodDesc = methodDesc; 30 | this.newSignature = newSignature; 31 | } 32 | 33 | @Override 34 | public void apply(HashMap classes) throws VerificationException { 35 | MethodNode methodNode = verifyNotNull(verifyNotNull(classes.get(className), "Class wasn't found").methods, "Field wasn't found") 36 | .stream() 37 | .filter(m -> m.name.equals(methodName) && m.desc.equals(methodDesc)) 38 | .findFirst() 39 | .orElseThrow(VerificationException::new); 40 | 41 | methodNode.signature = newSignature; 42 | } 43 | 44 | @Override 45 | public boolean canBeAppliedAtRuntime() { 46 | return true; 47 | } 48 | 49 | @Override 50 | public String getClassName() { 51 | return className; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/net/superblaubeere27/asmdelta/difference/methods/RemoveMethodDifference.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 superblaubeere27 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | * 6 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | */ 10 | 11 | package net.superblaubeere27.asmdelta.difference.methods; 12 | 13 | import net.superblaubeere27.asmdelta.difference.AbstractDifference; 14 | import net.superblaubeere27.asmdelta.difference.VerificationException; 15 | import org.objectweb.asm.tree.ClassNode; 16 | import org.objectweb.asm.tree.MethodNode; 17 | 18 | import java.util.HashMap; 19 | import java.util.List; 20 | import java.util.stream.Collectors; 21 | 22 | public class RemoveMethodDifference extends AbstractDifference { 23 | private String className; 24 | private String methodName; 25 | private String methodDesc; 26 | 27 | public RemoveMethodDifference(String className, String methodName, String methodDesc) { 28 | this.className = className; 29 | this.methodName = methodName; 30 | this.methodDesc = methodDesc; 31 | } 32 | 33 | @Override 34 | public void apply(HashMap classes) throws VerificationException { 35 | ClassNode classNode = classes.get(className); 36 | 37 | List methods = classNode.methods; 38 | 39 | if (methods == null) { // According to the documentation, methods might be null 40 | throw new VerificationException("Class has no methods to remove"); 41 | } 42 | 43 | List toRemove = methods.stream().filter(method -> method.name.equals(methodName) && method.desc.equals(methodDesc)).collect(Collectors.toList()); 44 | if (toRemove.isEmpty()) { 45 | //throw new VerificationException("Method to remove " + methodName + " not found, " + methodDesc); 46 | System.err.println("Method to remove " + methodName + " not found, " + methodDesc); 47 | } 48 | methods.removeAll(toRemove); 49 | } 50 | 51 | @Override 52 | public String getClassName() { 53 | return className; 54 | } 55 | 56 | @Override 57 | public boolean canBeAppliedAtRuntime() { 58 | return false; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/net/superblaubeere27/asmdelta/utils/Hex.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 superblaubeere27 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | * 6 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | */ 10 | 11 | package net.superblaubeere27.asmdelta.utils; 12 | 13 | import java.io.IOException; 14 | import java.nio.ByteBuffer; 15 | 16 | /** 17 | * Converts hexadecimal Strings. The charset used for certain operation can be set. 18 | *

19 | * This class is thread-safe. 20 | * 21 | * @since 1.1 22 | */ 23 | public class Hex { 24 | 25 | /** 26 | * Used to build output as Hex 27 | */ 28 | private static final char[] DIGITS_LOWER = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 29 | 'e', 'f'}; 30 | 31 | /** 32 | * Used to build output as Hex 33 | */ 34 | private static final char[] DIGITS_UPPER = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 35 | 'E', 'F'}; 36 | 37 | public static byte[] decodeHex(final String data) throws IOException { 38 | return decodeHex(data.toCharArray()); 39 | } 40 | 41 | public static byte[] decodeHex(final char[] data) throws IOException { 42 | 43 | final int len = data.length; 44 | 45 | if ((len & 0x01) != 0) { 46 | throw new IOException("Odd number of characters."); 47 | } 48 | 49 | final byte[] out = new byte[len >> 1]; 50 | 51 | // two characters form the hex value. 52 | for (int i = 0, j = 0; j < len; i++) { 53 | int f = toDigit(data[j], j) << 4; 54 | j++; 55 | f = f | toDigit(data[j], j); 56 | j++; 57 | out[i] = (byte) (f & 0xFF); 58 | } 59 | 60 | return out; 61 | } 62 | 63 | /** 64 | * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order. 65 | * The returned array will be double the length of the passed array, as it takes two characters to represent any 66 | * given byte. 67 | * 68 | * @param data a byte[] to convert to Hex characters 69 | * @return A char[] containing lower-case hexadecimal characters 70 | */ 71 | public static char[] encodeHex(final byte[] data) { 72 | return encodeHex(data, true); 73 | } 74 | 75 | /** 76 | * Converts a byte buffer into an array of characters representing the hexadecimal values of each byte in order. The 77 | * returned array will be double the length of the passed array, as it takes two characters to represent any given 78 | * byte. 79 | * 80 | * @param data a byte buffer to convert to Hex characters 81 | * @return A char[] containing lower-case hexadecimal characters 82 | * @since 1.11 83 | */ 84 | public static char[] encodeHex(final ByteBuffer data) { 85 | return encodeHex(data, true); 86 | } 87 | 88 | /** 89 | * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order. 90 | * The returned array will be double the length of the passed array, as it takes two characters to represent any 91 | * given byte. 92 | * 93 | * @param data a byte[] to convert to Hex characters 94 | * @param toLowerCase true converts to lowercase, false to uppercase 95 | * @return A char[] containing hexadecimal characters in the selected case 96 | * @since 1.4 97 | */ 98 | public static char[] encodeHex(final byte[] data, final boolean toLowerCase) { 99 | return encodeHex(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER); 100 | } 101 | 102 | /** 103 | * Converts a byte buffer into an array of characters representing the hexadecimal values of each byte in order. The 104 | * returned array will be double the length of the passed array, as it takes two characters to represent any given 105 | * byte. 106 | * 107 | * @param data a byte buffer to convert to Hex characters 108 | * @param toLowerCase true converts to lowercase, false to uppercase 109 | * @return A char[] containing hexadecimal characters in the selected case 110 | * @since 1.11 111 | */ 112 | public static char[] encodeHex(final ByteBuffer data, final boolean toLowerCase) { 113 | return encodeHex(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER); 114 | } 115 | 116 | /** 117 | * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order. 118 | * The returned array will be double the length of the passed array, as it takes two characters to represent any 119 | * given byte. 120 | * 121 | * @param data a byte[] to convert to Hex characters 122 | * @param toDigits the output alphabet (must contain at least 16 chars) 123 | * @return A char[] containing the appropriate characters from the alphabet For best results, this should be either 124 | * upper- or lower-case hex. 125 | * @since 1.4 126 | */ 127 | protected static char[] encodeHex(final byte[] data, final char[] toDigits) { 128 | final int l = data.length; 129 | final char[] out = new char[l << 1]; 130 | // two characters form the hex value. 131 | for (int i = 0, j = 0; i < l; i++) { 132 | out[j++] = toDigits[(0xF0 & data[i]) >>> 4]; 133 | out[j++] = toDigits[0x0F & data[i]]; 134 | } 135 | return out; 136 | } 137 | 138 | /** 139 | * Converts a byte buffer into an array of characters representing the hexadecimal values of each byte in order. The 140 | * returned array will be double the length of the passed array, as it takes two characters to represent any given 141 | * byte. 142 | * 143 | * @param byteBuffer a byte buffer to convert to Hex characters 144 | * @param toDigits the output alphabet (must be at least 16 characters) 145 | * @return A char[] containing the appropriate characters from the alphabet For best results, this should be either 146 | * upper- or lower-case hex. 147 | * @since 1.11 148 | */ 149 | protected static char[] encodeHex(final ByteBuffer byteBuffer, final char[] toDigits) { 150 | return encodeHex(toByteArray(byteBuffer), toDigits); 151 | } 152 | 153 | /** 154 | * Converts an array of bytes into a String representing the hexadecimal values of each byte in order. The returned 155 | * String will be double the length of the passed array, as it takes two characters to represent any given byte. 156 | * 157 | * @param data a byte[] to convert to Hex characters 158 | * @return A String containing lower-case hexadecimal characters 159 | * @since 1.4 160 | */ 161 | public static String encodeHexString(final byte[] data) { 162 | return new String(encodeHex(data)); 163 | } 164 | 165 | /** 166 | * Converts an array of bytes into a String representing the hexadecimal values of each byte in order. The returned 167 | * String will be double the length of the passed array, as it takes two characters to represent any given byte. 168 | * 169 | * @param data a byte[] to convert to Hex characters 170 | * @param toLowerCase true converts to lowercase, false to uppercase 171 | * @return A String containing lower-case hexadecimal characters 172 | * @since 1.11 173 | */ 174 | public static String encodeHexString(final byte[] data, final boolean toLowerCase) { 175 | return new String(encodeHex(data, toLowerCase)); 176 | } 177 | 178 | /** 179 | * Converts a byte buffer into a String representing the hexadecimal values of each byte in order. The returned 180 | * String will be double the length of the passed array, as it takes two characters to represent any given byte. 181 | * 182 | * @param data a byte buffer to convert to Hex characters 183 | * @return A String containing lower-case hexadecimal characters 184 | * @since 1.11 185 | */ 186 | public static String encodeHexString(final ByteBuffer data) { 187 | return new String(encodeHex(data)); 188 | } 189 | 190 | /** 191 | * Converts a byte buffer into a String representing the hexadecimal values of each byte in order. The returned 192 | * String will be double the length of the passed array, as it takes two characters to represent any given byte. 193 | * 194 | * @param data a byte buffer to convert to Hex characters 195 | * @param toLowerCase true converts to lowercase, false to uppercase 196 | * @return A String containing lower-case hexadecimal characters 197 | * @since 1.11 198 | */ 199 | public static String encodeHexString(final ByteBuffer data, final boolean toLowerCase) { 200 | return new String(encodeHex(data, toLowerCase)); 201 | } 202 | 203 | private static byte[] toByteArray(final ByteBuffer byteBuffer) { 204 | if (byteBuffer.hasArray()) { 205 | return byteBuffer.array(); 206 | } 207 | final byte[] byteArray = new byte[byteBuffer.remaining()]; 208 | byteBuffer.get(byteArray); 209 | return byteArray; 210 | } 211 | 212 | /** 213 | * Converts a hexadecimal character to an integer. 214 | * 215 | * @param ch A character to convert to an integer digit 216 | * @param index The index of the character in the source 217 | * @return An integer 218 | * @throws IOException Thrown if ch is an illegal hex character 219 | */ 220 | protected static int toDigit(final char ch, final int index) throws IOException { 221 | final int digit = Character.digit(ch, 16); 222 | if (digit == -1) { 223 | throw new IOException("Illegal hexadecimal character " + ch + " at index " + index); 224 | } 225 | return digit; 226 | } 227 | 228 | } 229 | -------------------------------------------------------------------------------- /src/main/java/net/superblaubeere27/asmdelta/utils/InstructionComparator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 superblaubeere27 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | * 6 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | */ 10 | 11 | package net.superblaubeere27.asmdelta.utils; 12 | 13 | import org.objectweb.asm.tree.*; 14 | 15 | import java.util.Arrays; 16 | import java.util.HashMap; 17 | import java.util.List; 18 | import java.util.Objects; 19 | import java.util.stream.Collectors; 20 | 21 | public class InstructionComparator { 22 | 23 | public static boolean isSame(InsnList insnListA, InsnList insnListB, List tryCatchA, List tryCatchB) { 24 | if (insnListA.size() != insnListB.size() || tryCatchA.size() != tryCatchB.size()) { 25 | return false; 26 | } 27 | 28 | HashMap labelIndexMap = calulateLabelIdecies(insnListA); 29 | labelIndexMap.putAll(calulateLabelIdecies(insnListB)); 30 | 31 | AbstractInsnNode[] abstractInsnNodesA = insnListA.toArray(); 32 | AbstractInsnNode[] abstractInsnNodesB = insnListB.toArray(); 33 | 34 | for (int i = 0; i < abstractInsnNodesA.length; i++) { 35 | AbstractInsnNode a = abstractInsnNodesA[i]; 36 | AbstractInsnNode b = abstractInsnNodesB[i]; 37 | 38 | if (!isSame(labelIndexMap, a, b)) 39 | return false; 40 | } 41 | 42 | for (int i = 0; i < tryCatchA.size(); i++) { 43 | TryCatchBlockNode a = tryCatchA.get(i); 44 | TryCatchBlockNode b = tryCatchB.get(i); 45 | 46 | if (!Objects.equals(a.type, b.type) 47 | || !labelIndexMap.get(a.start).equals(labelIndexMap.get(b.start)) 48 | || !labelIndexMap.get(a.end).equals(labelIndexMap.get(b.end)) 49 | || !labelIndexMap.get(a.handler).equals(labelIndexMap.get(b.handler))) { 50 | return false; 51 | } 52 | } 53 | 54 | return true; 55 | } 56 | 57 | private static boolean isSame(HashMap labelIndexMap, AbstractInsnNode a, AbstractInsnNode b) { 58 | if (a.getOpcode() != b.getOpcode() || a.getType() != b.getType() || a.getClass() != b.getClass()) { 59 | return false; 60 | } 61 | 62 | if (a instanceof FieldInsnNode) { 63 | FieldInsnNode fieldInsnNodeA = (FieldInsnNode) a; 64 | FieldInsnNode fieldInsnNodeB = (FieldInsnNode) b; 65 | 66 | return fieldInsnNodeA.owner.equals(fieldInsnNodeB.owner) 67 | && fieldInsnNodeA.name.equals(fieldInsnNodeB.name) 68 | && fieldInsnNodeA.desc.equals(fieldInsnNodeB.desc); 69 | } 70 | if (a instanceof MethodInsnNode) { 71 | MethodInsnNode methodInsnNodeA = (MethodInsnNode) a; 72 | MethodInsnNode methodInsnNodeB = (MethodInsnNode) b; 73 | 74 | return methodInsnNodeA.owner.equals(methodInsnNodeB.owner) 75 | && methodInsnNodeA.name.equals(methodInsnNodeB.name) 76 | && methodInsnNodeA.desc.equals(methodInsnNodeB.desc) 77 | && methodInsnNodeA.itf == methodInsnNodeB.itf; 78 | } 79 | if (a instanceof TableSwitchInsnNode) { 80 | TableSwitchInsnNode switchA = (TableSwitchInsnNode) a; 81 | TableSwitchInsnNode switchB = (TableSwitchInsnNode) b; 82 | 83 | if (!labelIndexMap.get(switchA.dflt).equals(labelIndexMap.get(switchB.dflt))) { 84 | return false; 85 | } 86 | 87 | if (switchA.min != switchB.min || switchA.max != switchB.max) { 88 | return false; 89 | } 90 | 91 | return switchA.labels 92 | .stream() 93 | .map(labelIndexMap::get) 94 | .collect(Collectors.toList()) 95 | .equals(switchB.labels 96 | .stream() 97 | .map(labelIndexMap::get) 98 | .collect(Collectors.toList())); 99 | 100 | 101 | } 102 | if (a instanceof LineNumberNode) return true; 103 | if (a instanceof IincInsnNode) { 104 | IincInsnNode incA = (IincInsnNode) a; 105 | IincInsnNode incB = (IincInsnNode) b; 106 | 107 | return incA.incr == incB.incr && incA.var == incB.var; 108 | } 109 | if (a instanceof IntInsnNode) { 110 | return ((IntInsnNode) a).operand == ((IntInsnNode) b).operand; 111 | } 112 | if (a instanceof LabelNode) { 113 | return true; 114 | } 115 | if (a instanceof MultiANewArrayInsnNode) { 116 | MultiANewArrayInsnNode incA = (MultiANewArrayInsnNode) a; 117 | MultiANewArrayInsnNode incB = (MultiANewArrayInsnNode) b; 118 | 119 | return incA.desc.equals(incB.desc) && incA.dims == incB.dims; 120 | } 121 | if (a instanceof LdcInsnNode) { 122 | return ((LdcInsnNode) a).cst.equals(((LdcInsnNode) b).cst); 123 | } 124 | if (a instanceof TypeInsnNode) { 125 | return ((TypeInsnNode) a).desc.equals(((TypeInsnNode) b).desc); 126 | } 127 | if (a instanceof VarInsnNode) { 128 | return ((VarInsnNode) a).var == ((VarInsnNode) b).var; 129 | } 130 | if (a instanceof InvokeDynamicInsnNode) { 131 | InvokeDynamicInsnNode incA = (InvokeDynamicInsnNode) a; 132 | InvokeDynamicInsnNode incB = (InvokeDynamicInsnNode) b; 133 | 134 | return incA.bsm.equals(incB.bsm) && Arrays.equals(incA.bsmArgs, incB.bsmArgs) && incA.desc.equals(incB.desc) && incA.name.equals(incB.name); 135 | } 136 | if (a instanceof FrameNode) { 137 | return true; // Assuming true since if all instructions are the same, the frame can't be different 138 | } 139 | if (a instanceof JumpInsnNode) { 140 | JumpInsnNode incA = (JumpInsnNode) a; 141 | JumpInsnNode incB = (JumpInsnNode) b; 142 | 143 | return labelIndexMap.get(incA.label).equals(labelIndexMap.get(incB.label)); 144 | } 145 | if (a instanceof LookupSwitchInsnNode) { 146 | LookupSwitchInsnNode switchA = (LookupSwitchInsnNode) a; 147 | LookupSwitchInsnNode switchB = (LookupSwitchInsnNode) b; 148 | 149 | if (!labelIndexMap.get(switchA.dflt).equals(labelIndexMap.get(switchB.dflt))) { 150 | return false; 151 | } 152 | 153 | if (!switchA.keys.equals(switchB.keys)) { 154 | return false; 155 | } 156 | 157 | return switchA.labels 158 | .stream() 159 | .map(labelIndexMap::get) 160 | .collect(Collectors.toList()) 161 | .equals(switchB.labels 162 | .stream() 163 | .map(labelIndexMap::get) 164 | .collect(Collectors.toList())); 165 | } 166 | 167 | return true; 168 | } 169 | 170 | private static HashMap calulateLabelIdecies(InsnList insnListA) { 171 | HashMap ret = new HashMap<>(); 172 | 173 | AbstractInsnNode[] abstractInsnNodes = insnListA.toArray(); 174 | 175 | for (int i = 0; i < abstractInsnNodes.length; i++) { 176 | if (abstractInsnNodes[i] instanceof LabelNode) { 177 | ret.put((LabelNode) abstractInsnNodes[i], i); 178 | } 179 | } 180 | 181 | return ret; 182 | } 183 | 184 | } 185 | -------------------------------------------------------------------------------- /src/main/java/net/superblaubeere27/asmdelta/utils/ScheduledRunnable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 superblaubeere27 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | * 6 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | */ 10 | 11 | package net.superblaubeere27.asmdelta.utils; 12 | 13 | public interface ScheduledRunnable { 14 | 15 | /** 16 | * @return Returns true if the thread is ready to exit 17 | */ 18 | boolean runTick(); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/net/superblaubeere27/asmdelta/utils/Scheduler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 superblaubeere27 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | * 6 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | */ 10 | 11 | package net.superblaubeere27.asmdelta.utils; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | public class Scheduler { 17 | private final List runningThreads = new ArrayList<>(); 18 | private ScheduledRunnable runnable; 19 | 20 | public Scheduler(ScheduledRunnable runnable) { 21 | this.runnable = runnable; 22 | } 23 | 24 | 25 | public void run(int threads) { 26 | for (int i = 0; i < threads; i++) { 27 | runningThreads.add(new Thread(() -> { 28 | while (true) { 29 | if (runnable.runTick()) break; 30 | } 31 | }, "Thread-" + i)); 32 | } 33 | 34 | runningThreads.forEach(Thread::start); 35 | } 36 | 37 | public void waitFor() { 38 | for (Thread runningThread : runningThreads) { 39 | try { 40 | runningThread.join(); 41 | } catch (InterruptedException ignored) { 42 | break; 43 | } 44 | } 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/net/superblaubeere27/asmdelta/utils/Utils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 superblaubeere27 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | * 6 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | */ 10 | 11 | package net.superblaubeere27.asmdelta.utils; 12 | 13 | import com.google.gson.Gson; 14 | import com.google.gson.GsonBuilder; 15 | import net.superblaubeere27.asmdelta.difference.AbstractDifference; 16 | import net.superblaubeere27.asmdelta.difference.methods.MethodLocalVariableDifference; 17 | import net.superblaubeere27.asmdelta.utils.typeadapter.AbstractDifferenceSerializer; 18 | import net.superblaubeere27.asmdelta.utils.typeadapter.ClassNodeSerializer; 19 | import net.superblaubeere27.asmdelta.utils.typeadapter.MethodLocalVariableDifferenceSerializer; 20 | import net.superblaubeere27.asmdelta.utils.typeadapter.MethodNodeSerializer; 21 | import org.objectweb.asm.tree.ClassNode; 22 | import org.objectweb.asm.tree.InsnList; 23 | import org.objectweb.asm.tree.MethodNode; 24 | import org.objectweb.asm.util.Printer; 25 | import org.objectweb.asm.util.Textifier; 26 | import org.objectweb.asm.util.TraceClassVisitor; 27 | import org.objectweb.asm.util.TraceMethodVisitor; 28 | 29 | import java.io.PrintWriter; 30 | import java.io.StringWriter; 31 | 32 | public class Utils { 33 | public static final Gson GSON; 34 | 35 | static { 36 | GSON = new GsonBuilder() 37 | .registerTypeAdapter(ClassNode.class, new ClassNodeSerializer()) 38 | .registerTypeAdapter(MethodNode.class, new MethodNodeSerializer()) 39 | .registerTypeAdapter(AbstractDifference.class, new AbstractDifferenceSerializer()) 40 | .registerTypeAdapter(MethodLocalVariableDifference.class, new MethodLocalVariableDifferenceSerializer()) 41 | .create(); 42 | } 43 | 44 | public static String prettyprint(MethodNode insnNode) { 45 | final Printer printer = new Textifier(); 46 | TraceMethodVisitor methodPrinter = new TraceMethodVisitor(printer); 47 | insnNode.accept(methodPrinter); 48 | StringWriter sw = new StringWriter(); 49 | printer.print(new PrintWriter(sw)); 50 | printer.getText().clear(); 51 | return sw.toString().trim(); 52 | } 53 | 54 | public static String prettyprint(InsnList insnNode) { 55 | final Printer printer = new Textifier(); 56 | TraceMethodVisitor methodPrinter = new TraceMethodVisitor(printer); 57 | insnNode.accept(methodPrinter); 58 | StringWriter sw = new StringWriter(); 59 | printer.print(new PrintWriter(sw)); 60 | printer.getText().clear(); 61 | return sw.toString().trim(); 62 | } 63 | 64 | public static String prettyprint(ClassNode insnNode) { 65 | StringWriter sw = new StringWriter(); 66 | TraceClassVisitor methodPrinter = new TraceClassVisitor(new PrintWriter(sw)); 67 | insnNode.accept(methodPrinter); 68 | // printer.print(new PrintWriter(sw)); 69 | // printer.getText().clear(); 70 | return sw.toString().trim(); 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/net/superblaubeere27/asmdelta/utils/typeadapter/AbstractDifferenceSerializer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 superblaubeere27 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | * 6 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | */ 10 | 11 | package net.superblaubeere27.asmdelta.utils.typeadapter; 12 | 13 | import com.google.gson.*; 14 | import net.superblaubeere27.asmdelta.difference.AbstractDifference; 15 | 16 | import java.lang.reflect.Type; 17 | 18 | public class AbstractDifferenceSerializer implements JsonSerializer, JsonDeserializer { 19 | 20 | private static final String CLASS_META_KEY = "CLASS_META_KEY"; 21 | 22 | @Override 23 | public AbstractDifference deserialize(JsonElement jsonElement, Type type, 24 | JsonDeserializationContext jsonDeserializationContext) 25 | throws JsonParseException { 26 | JsonObject jsonObj = jsonElement.getAsJsonObject(); 27 | String className = jsonObj.get(CLASS_META_KEY).getAsString(); 28 | if (className.equals(MethodLocalVariableDifferenceSerializer.class.getCanonicalName())) { 29 | return (new MethodLocalVariableDifferenceSerializer()).deserialize(jsonElement, type, jsonDeserializationContext); 30 | } 31 | try { 32 | Class clz = Class.forName(className); 33 | return jsonDeserializationContext.deserialize(jsonElement, clz); 34 | } catch (ClassNotFoundException e) { 35 | throw new JsonParseException(e); 36 | } 37 | } 38 | 39 | @Override 40 | public JsonElement serialize(AbstractDifference object, Type type, 41 | JsonSerializationContext jsonSerializationContext) { 42 | JsonElement jsonEle = jsonSerializationContext.serialize(object, object.getClass()); 43 | jsonEle.getAsJsonObject().addProperty(CLASS_META_KEY, 44 | object.getClass().getCanonicalName()); 45 | return jsonEle; 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/net/superblaubeere27/asmdelta/utils/typeadapter/ClassNodeSerializer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 superblaubeere27 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | * 6 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | */ 10 | 11 | package net.superblaubeere27.asmdelta.utils.typeadapter; 12 | 13 | import com.google.gson.*; 14 | import net.superblaubeere27.asmdelta.utils.Hex; 15 | import org.objectweb.asm.ClassReader; 16 | import org.objectweb.asm.ClassWriter; 17 | import org.objectweb.asm.tree.ClassNode; 18 | 19 | import java.io.IOException; 20 | import java.lang.reflect.Type; 21 | 22 | public class ClassNodeSerializer implements JsonSerializer, JsonDeserializer { 23 | 24 | @Override 25 | public JsonElement serialize(ClassNode classNode, Type type, JsonSerializationContext jsonSerializationContext) { 26 | ClassWriter classWriter = new ClassWriter(0); 27 | 28 | 29 | classNode.accept(classWriter); 30 | 31 | return new JsonPrimitive(Hex.encodeHexString(classWriter.toByteArray())); 32 | } 33 | 34 | @Override 35 | public ClassNode deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { 36 | ClassNode classNode = new ClassNode(); 37 | 38 | ClassReader cr; 39 | 40 | try { 41 | cr = new ClassReader(Hex.decodeHex(jsonElement.getAsString())); 42 | } catch (IOException e) { 43 | throw new JsonParseException(e); 44 | } 45 | 46 | cr.accept(classNode, 0); 47 | 48 | return classNode; 49 | } 50 | } -------------------------------------------------------------------------------- /src/main/java/net/superblaubeere27/asmdelta/utils/typeadapter/MethodLocalVariableDifferenceSerializer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 superblaubeere27 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | * 6 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | */ 10 | 11 | package net.superblaubeere27.asmdelta.utils.typeadapter; 12 | 13 | import com.google.gson.*; 14 | import net.superblaubeere27.asmdelta.difference.AbstractDifference; 15 | import net.superblaubeere27.asmdelta.difference.methods.MethodLocalVariableDifference; 16 | import net.superblaubeere27.asmdelta.utils.Hex; 17 | import org.objectweb.asm.ClassReader; 18 | import org.objectweb.asm.ClassWriter; 19 | import org.objectweb.asm.Opcodes; 20 | import org.objectweb.asm.tree.ClassNode; 21 | import org.objectweb.asm.tree.MethodNode; 22 | 23 | import java.io.IOException; 24 | import java.lang.reflect.Type; 25 | import java.util.Collections; 26 | import java.util.Objects; 27 | 28 | public class MethodLocalVariableDifferenceSerializer implements JsonSerializer, JsonDeserializer { 29 | 30 | private static final String CLASS_META_KEY = "CLASS_META_KEY"; 31 | 32 | @Override 33 | public JsonElement serialize(MethodLocalVariableDifference methodLocalVariableDifference, java.lang.reflect.Type type, JsonSerializationContext jsonSerializationContext) { 34 | ClassNode classNode = new ClassNode(); 35 | 36 | classNode.version = Opcodes.V17; 37 | classNode.access = Opcodes.ACC_PRIVATE; 38 | classNode.name = methodLocalVariableDifference.getClassName(); 39 | classNode.signature = null; 40 | classNode.superName = "java/lang/Object"; 41 | classNode.interfaces = Collections.emptyList(); 42 | 43 | MethodNode methodNode = new MethodNode(Opcodes.ACC_PUBLIC, methodLocalVariableDifference.methodName, methodLocalVariableDifference.methodDesc, null, null); 44 | methodNode.localVariables = methodLocalVariableDifference.localVariables; 45 | 46 | methodNode.accept(classNode); 47 | 48 | ClassWriter classWriter = new ClassWriter(0); 49 | 50 | 51 | classNode.accept(classWriter); 52 | 53 | JsonElement ele = new JsonObject(); 54 | ele.getAsJsonObject().addProperty("content", Hex.encodeHexString(classWriter.toByteArray())); 55 | ele.getAsJsonObject().addProperty(CLASS_META_KEY, 56 | this.getClass().getCanonicalName()); 57 | return ele; 58 | } 59 | 60 | @Override 61 | public MethodLocalVariableDifference deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { 62 | ClassNode classNode = new ClassNode(); 63 | 64 | ClassReader cr; 65 | 66 | try { 67 | cr = new ClassReader(Hex.decodeHex(jsonElement.getAsJsonObject().get("content").getAsString())); 68 | } catch (IOException e) { 69 | System.err.println("Error while parsing method node"); 70 | throw new JsonParseException(e); 71 | } 72 | 73 | cr.accept(classNode, 0); 74 | 75 | if (Objects.requireNonNull(classNode.methods).size() != 1) { 76 | System.err.println("Error while parsing method node"); 77 | throw new JsonParseException("Class has more than a method"); 78 | } 79 | return new MethodLocalVariableDifference(classNode.name, classNode.methods.get(0).name, classNode.methods.get(0).desc, classNode.methods.get(0).localVariables); 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/net/superblaubeere27/asmdelta/utils/typeadapter/MethodNodeSerializer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 superblaubeere27 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | * 6 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | */ 10 | 11 | package net.superblaubeere27.asmdelta.utils.typeadapter; 12 | 13 | import com.google.gson.*; 14 | import net.superblaubeere27.asmdelta.utils.Hex; 15 | import org.objectweb.asm.ClassReader; 16 | import org.objectweb.asm.ClassWriter; 17 | import org.objectweb.asm.Opcodes; 18 | import org.objectweb.asm.tree.ClassNode; 19 | import org.objectweb.asm.tree.MethodNode; 20 | 21 | import java.io.IOException; 22 | import java.lang.reflect.Type; 23 | import java.util.Collections; 24 | import java.util.Objects; 25 | 26 | public class MethodNodeSerializer implements JsonSerializer, JsonDeserializer { 27 | 28 | @Override 29 | public JsonElement serialize(MethodNode methodNode, java.lang.reflect.Type type, JsonSerializationContext jsonSerializationContext) { 30 | ClassNode classNode = new ClassNode(); 31 | 32 | classNode.version = Opcodes.V17; 33 | classNode.access = Opcodes.ACC_PRIVATE; 34 | classNode.name = "asdf"; 35 | classNode.signature = null; 36 | classNode.superName = "java/lang/Object"; 37 | classNode.interfaces = Collections.emptyList(); 38 | 39 | methodNode.accept(classNode); 40 | 41 | ClassWriter classWriter = new ClassWriter(0); 42 | 43 | 44 | classNode.accept(classWriter); 45 | 46 | return new JsonPrimitive(Hex.encodeHexString(classWriter.toByteArray())); 47 | } 48 | 49 | @Override 50 | public MethodNode deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { 51 | ClassNode classNode = new ClassNode(); 52 | 53 | ClassReader cr; 54 | 55 | try { 56 | cr = new ClassReader(Hex.decodeHex(jsonElement.getAsString())); 57 | } catch (IOException e) { 58 | System.err.println("Error while parsing method node"); 59 | throw new JsonParseException(e); 60 | } 61 | 62 | cr.accept(classNode, 0); 63 | 64 | if (Objects.requireNonNull(classNode.methods).size() != 1) { 65 | System.err.println("Error while parsing method node"); 66 | throw new JsonParseException("Class has more than a method"); 67 | } 68 | 69 | return classNode.methods.get(0); 70 | } 71 | } -------------------------------------------------------------------------------- /src/main/resources/cursedbtw.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "minVersion": "0.8", 4 | "package": "btw.community.fabric.mixin", 5 | "compatibilityLevel": "JAVA_17", 6 | "mixins": [ 7 | "MinecraftServerMixin" 8 | ], 9 | "client": [ 10 | "MinecraftMixin", 11 | "NetClientHandlerMixin" 12 | ], 13 | "injectors": { 14 | "defaultRequire": 1 15 | }, 16 | "plugin": "net.devtech.grossfabrichacks.mixin.GrossFabricHacksPlugin" 17 | } 18 | -------------------------------------------------------------------------------- /src/main/resources/fabric.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "id": "btw", 4 | "name": "Better Than Wolves CE", 5 | "version": "3.0.0 Beta Snapshot 3", 6 | "environment": "*", 7 | "description": "This is a continuation of FlowerChild's Better than Wolves total conversion Minecraft mod.", 8 | "contact": { 9 | "issues": "https://github.com/BTW-Community/BTW-Public/issues", 10 | "sources": "https://github.com/BTW-Community/BTW-Public" 11 | }, 12 | "authors": [ 13 | "FlowerChild", 14 | "Better than Wolves Community" 15 | ], 16 | "license": "Creative Commons Attribution 4.0 International", 17 | "icon": "btw_logo.png", 18 | "entrypoints": { 19 | "init": [ 20 | "btw.community.fabric.BTWFabricMod" 21 | ], 22 | "gfh:prePreLaunch": [ 23 | "btw.community.fabric.BTWFabricMod" 24 | ], 25 | "btw:addon": [ 26 | "btw.BTWMod", 27 | "emi.dev.emi.emi.EMIPostInit" 28 | ], 29 | "emi": [ 30 | "emi.dev.emi.emi.api.plugin.VanillaPlugin", 31 | "emi.dev.emi.emi.api.plugin.BTWPlugin" 32 | ] 33 | }, 34 | "mixins": [ 35 | "cursedbtw.mixins.json" 36 | ], 37 | 38 | "depends": { 39 | "fabricloader": ">=0.7.4" 40 | }, 41 | "suggests": { 42 | "flamingo": "*" 43 | }, 44 | "custom": { 45 | "btw:addon_prefix": "BTW" 46 | } 47 | } --------------------------------------------------------------------------------