mirror of
https://github.com/ReVanced/revanced-patcher.git
synced 2026-01-19 17:33:56 +00:00
Compare commits
31 Commits
v11.0.2-de
...
v12.1.0-de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc28d414dc | ||
|
|
9755bab298 | ||
|
|
fae4029cfc | ||
|
|
1790f0d706 | ||
|
|
0ba2c51676 | ||
|
|
03cd97b49c | ||
|
|
16a162c1dd | ||
|
|
c9bbcf2bf2 | ||
|
|
86e1bf6078 | ||
|
|
1bca84ef0b | ||
|
|
e0f8e1b71a | ||
|
|
416d69142f | ||
|
|
426807aeaa | ||
|
|
90cb075a97 | ||
|
|
ac2ca8fbd3 | ||
|
|
69e4a49065 | ||
|
|
a4a030f2b2 | ||
|
|
dcc4ecd237 | ||
|
|
57f3036a96 | ||
|
|
753e55dfc3 | ||
|
|
9d81baf4b4 | ||
|
|
7cb4d4c596 | ||
|
|
2c8565508e | ||
|
|
c7f156e4c9 | ||
|
|
fcef4342e8 | ||
|
|
72783a5e74 | ||
|
|
a379b69eeb | ||
|
|
0a8ccba33e | ||
|
|
519359a9eb | ||
|
|
b615ed6aab | ||
|
|
d718134ab2 |
9
.gitattributes
vendored
Normal file
9
.gitattributes
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#
|
||||||
|
# https://help.github.com/articles/dealing-with-line-endings/
|
||||||
|
#
|
||||||
|
# Linux start script should use lf
|
||||||
|
/gradlew text eol=lf
|
||||||
|
|
||||||
|
# These are Windows script files and should use crlf
|
||||||
|
*.bat text eol=crlf
|
||||||
|
|
||||||
@@ -7,7 +7,13 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"@semantic-release/commit-analyzer",
|
[
|
||||||
|
"@semantic-release/commit-analyzer", {
|
||||||
|
"releaseRules": [
|
||||||
|
{ "type": "build", "scope": "Needs bump", "release": "patch" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
"@semantic-release/release-notes-generator",
|
"@semantic-release/release-notes-generator",
|
||||||
"@semantic-release/changelog",
|
"@semantic-release/changelog",
|
||||||
"gradle-semantic-release-plugin",
|
"gradle-semantic-release-plugin",
|
||||||
|
|||||||
108
CHANGELOG.md
108
CHANGELOG.md
@@ -1,3 +1,111 @@
|
|||||||
|
# [12.1.0-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v12.0.0...v12.1.0-dev.1) (2023-08-01)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add `MutableMethod.getInstructions` extension function ([fae4029](https://github.com/ReVanced/revanced-patcher/commit/fae4029cfccfad7aa3dd8f7fbef1c63ee26b85b3))
|
||||||
|
|
||||||
|
# [12.0.0](https://github.com/ReVanced/revanced-patcher/compare/v11.0.4...v12.0.0) (2023-07-30)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* correct access flags of `PackageMetadata` ([416d691](https://github.com/ReVanced/revanced-patcher/commit/416d69142f50dab49c9ea3f027e9d53e4777f257))
|
||||||
|
* set resource table via resource decoder ([e0f8e1b](https://github.com/ReVanced/revanced-patcher/commit/e0f8e1b71a295948b610029c89a48f52762396b6))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Deprecate `Version` annotation ([c9bbcf2](https://github.com/ReVanced/revanced-patcher/commit/c9bbcf2bf2b0f50ab9100380a3a66c6346ad42ac))
|
||||||
|
* remove `Path` option ([#202](https://github.com/ReVanced/revanced-patcher/issues/202)) ([69e4a49](https://github.com/ReVanced/revanced-patcher/commit/69e4a490659ebc4fb4bf46148634f4b064ef1713))
|
||||||
|
|
||||||
|
|
||||||
|
### BREAKING CHANGES
|
||||||
|
|
||||||
|
* This removes the previously available `Path` option
|
||||||
|
|
||||||
|
# [12.0.0-dev.2](https://github.com/ReVanced/revanced-patcher/compare/v12.0.0-dev.1...v12.0.0-dev.2) (2023-07-28)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Deprecate `Version` annotation ([400442f](https://github.com/ReVanced/revanced-patcher/commit/400442f70ee56cafd4493b2ce64a294db9836509))
|
||||||
|
|
||||||
|
# [12.0.0-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v11.0.4...v12.0.0-dev.1) (2023-07-26)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* correct access flags of `PackageMetadata` ([416d691](https://github.com/ReVanced/revanced-patcher/commit/416d69142f50dab49c9ea3f027e9d53e4777f257))
|
||||||
|
* set resource table via resource decoder ([e0f8e1b](https://github.com/ReVanced/revanced-patcher/commit/e0f8e1b71a295948b610029c89a48f52762396b6))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* remove `Path` option ([#202](https://github.com/ReVanced/revanced-patcher/issues/202)) ([69e4a49](https://github.com/ReVanced/revanced-patcher/commit/69e4a490659ebc4fb4bf46148634f4b064ef1713))
|
||||||
|
|
||||||
|
|
||||||
|
### BREAKING CHANGES
|
||||||
|
|
||||||
|
* This removes the previously available `Path` option
|
||||||
|
|
||||||
|
## [11.0.4](https://github.com/revanced/revanced-patcher/compare/v11.0.3...v11.0.4) (2023-07-01)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* clear method lookup maps ([#198](https://github.com/revanced/revanced-patcher/issues/198)) ([9d81baf](https://github.com/revanced/revanced-patcher/commit/9d81baf4b4ca7514f8a1009e72218638609a7c7f))
|
||||||
|
|
||||||
|
## [11.0.4-dev.1](https://github.com/revanced/revanced-patcher/compare/v11.0.3...v11.0.4-dev.1) (2023-07-01)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* clear method lookup maps ([#198](https://github.com/revanced/revanced-patcher/issues/198)) ([9d81baf](https://github.com/revanced/revanced-patcher/commit/9d81baf4b4ca7514f8a1009e72218638609a7c7f))
|
||||||
|
|
||||||
|
## [11.0.3](https://github.com/revanced/revanced-patcher/compare/v11.0.2...v11.0.3) (2023-06-30)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* NPE on method lookup ([#195](https://github.com/revanced/revanced-patcher/issues/195)) ([fcef434](https://github.com/revanced/revanced-patcher/commit/fcef4342e8bde73945e8315aef6337cc8a8d8572))
|
||||||
|
|
||||||
|
## [11.0.3-dev.1](https://github.com/revanced/revanced-patcher/compare/v11.0.2...v11.0.3-dev.1) (2023-06-28)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* NPE on method lookup ([#195](https://github.com/revanced/revanced-patcher/issues/195)) ([fcef434](https://github.com/revanced/revanced-patcher/commit/fcef4342e8bde73945e8315aef6337cc8a8d8572))
|
||||||
|
|
||||||
|
## [11.0.2](https://github.com/revanced/revanced-patcher/compare/v11.0.1...v11.0.2) (2023-06-27)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* catch exceptions from closing patches ([d5d6f85](https://github.com/revanced/revanced-patcher/commit/d5d6f85084c03ed9c776632823ca12394a716167))
|
||||||
|
* do not load annotations as patches ([519359a](https://github.com/revanced/revanced-patcher/commit/519359a9eb0e9dfa390c5016e9fe4a7490b8ab18))
|
||||||
|
* only close succeeded patches ([b8151eb](https://github.com/revanced/revanced-patcher/commit/b8151ebccb5b27dd9e06fa63235cf9baeef1c0ee))
|
||||||
|
* use `versionCode` if `versionName` is unavailable ([6e1b647](https://github.com/revanced/revanced-patcher/commit/6e1b6479b677657c226693e9cc6b63f4ef2ee060))
|
||||||
|
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* resolve fingerprints using method maps ([#185](https://github.com/revanced/revanced-patcher/issues/185)) ([d718134](https://github.com/revanced/revanced-patcher/commit/d718134ab26423e02708e01eba711737f9260ba0))
|
||||||
|
|
||||||
|
## [11.0.2-dev.4](https://github.com/revanced/revanced-patcher/compare/v11.0.2-dev.3...v11.0.2-dev.4) (2023-06-27)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* do not load annotations as patches ([519359a](https://github.com/revanced/revanced-patcher/commit/519359a9eb0e9dfa390c5016e9fe4a7490b8ab18))
|
||||||
|
|
||||||
|
## [11.0.2-dev.3](https://github.com/revanced/revanced-patcher/compare/v11.0.2-dev.2...v11.0.2-dev.3) (2023-06-27)
|
||||||
|
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* resolve fingerprints using method maps ([#185](https://github.com/revanced/revanced-patcher/issues/185)) ([d718134](https://github.com/revanced/revanced-patcher/commit/d718134ab26423e02708e01eba711737f9260ba0))
|
||||||
|
|
||||||
## [11.0.2-dev.2](https://github.com/revanced/revanced-patcher/compare/v11.0.2-dev.1...v11.0.2-dev.2) (2023-06-18)
|
## [11.0.2-dev.2](https://github.com/revanced/revanced-patcher/compare/v11.0.2-dev.1...v11.0.2-dev.2) (2023-06-18)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
plugins {
|
plugins {
|
||||||
kotlin("jvm") version "1.8.10"
|
kotlin("jvm") version "1.8.20"
|
||||||
`maven-publish`
|
`maven-publish`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ dependencies {
|
|||||||
implementation("xpp3:xpp3:1.1.4c")
|
implementation("xpp3:xpp3:1.1.4c")
|
||||||
implementation("app.revanced:smali:2.5.3-a3836654")
|
implementation("app.revanced:smali:2.5.3-a3836654")
|
||||||
implementation("app.revanced:multidexlib2:2.5.3-a3836654")
|
implementation("app.revanced:multidexlib2:2.5.3-a3836654")
|
||||||
implementation("app.revanced:apktool-lib:2.7.0")
|
implementation("app.revanced:apktool-lib:2.8.2")
|
||||||
|
|
||||||
implementation("org.jetbrains.kotlin:kotlin-reflect:1.8.20-RC")
|
implementation("org.jetbrains.kotlin:kotlin-reflect:1.8.20-RC")
|
||||||
testImplementation("org.jetbrains.kotlin:kotlin-test:1.8.20-RC")
|
testImplementation("org.jetbrains.kotlin:kotlin-test:1.8.20-RC")
|
||||||
|
|||||||
@@ -1,2 +1,4 @@
|
|||||||
|
org.gradle.parallel = true
|
||||||
|
org.gradle.caching = true
|
||||||
kotlin.code.style = official
|
kotlin.code.style = official
|
||||||
version = 11.0.2-dev.2
|
version = 12.1.0-dev.1
|
||||||
|
|||||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,7 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
|
||||||
|
networkTimeout=10000
|
||||||
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
40
gradlew
vendored
40
gradlew
vendored
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
#
|
#
|
||||||
# Copyright <EFBFBD> 2015-2021 the original authors.
|
# Copyright © 2015-2021 the original authors.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@@ -32,10 +32,10 @@
|
|||||||
# Busybox and similar reduced shells will NOT work, because this script
|
# Busybox and similar reduced shells will NOT work, because this script
|
||||||
# requires all of these POSIX shell features:
|
# requires all of these POSIX shell features:
|
||||||
# * functions;
|
# * functions;
|
||||||
# * expansions <EFBFBD>$var<EFBFBD>, <EFBFBD>${var}<EFBFBD>, <EFBFBD>${var:-default}<EFBFBD>, <EFBFBD>${var+SET}<EFBFBD>,
|
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||||
# <EFBFBD>${var#prefix}<EFBFBD>, <EFBFBD>${var%suffix}<EFBFBD>, and <EFBFBD>$( cmd )<EFBFBD>;
|
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||||
# * compound commands having a testable exit status, especially <EFBFBD>case<EFBFBD>;
|
# * compound commands having a testable exit status, especially «case»;
|
||||||
# * various built-in commands including <EFBFBD>command<EFBFBD>, <EFBFBD>set<EFBFBD>, and <EFBFBD>ulimit<EFBFBD>.
|
# * various built-in commands including «command», «set», and «ulimit».
|
||||||
#
|
#
|
||||||
# Important for patching:
|
# Important for patching:
|
||||||
#
|
#
|
||||||
@@ -55,7 +55,7 @@
|
|||||||
# Darwin, MinGW, and NonStop.
|
# Darwin, MinGW, and NonStop.
|
||||||
#
|
#
|
||||||
# (3) This script is generated from the Groovy template
|
# (3) This script is generated from the Groovy template
|
||||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
# within the Gradle project.
|
# within the Gradle project.
|
||||||
#
|
#
|
||||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
@@ -80,13 +80,10 @@ do
|
|||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
# This is normally unused
|
||||||
|
# shellcheck disable=SC2034
|
||||||
APP_NAME="Gradle"
|
|
||||||
APP_BASE_NAME=${0##*/}
|
APP_BASE_NAME=${0##*/}
|
||||||
|
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
|
||||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD=maximum
|
MAX_FD=maximum
|
||||||
@@ -133,22 +130,29 @@ location of your Java installation."
|
|||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
JAVACMD=java
|
JAVACMD=java
|
||||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
if ! command -v java >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
Please set the JAVA_HOME variable in your environment to match the
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
location of your Java installation."
|
location of your Java installation."
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Increase the maximum file descriptors if we can.
|
# Increase the maximum file descriptors if we can.
|
||||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
case $MAX_FD in #(
|
case $MAX_FD in #(
|
||||||
max*)
|
max*)
|
||||||
|
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC3045
|
||||||
MAX_FD=$( ulimit -H -n ) ||
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
warn "Could not query maximum file descriptor limit"
|
warn "Could not query maximum file descriptor limit"
|
||||||
esac
|
esac
|
||||||
case $MAX_FD in #(
|
case $MAX_FD in #(
|
||||||
'' | soft) :;; #(
|
'' | soft) :;; #(
|
||||||
*)
|
*)
|
||||||
|
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC3045
|
||||||
ulimit -n "$MAX_FD" ||
|
ulimit -n "$MAX_FD" ||
|
||||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
esac
|
esac
|
||||||
@@ -193,6 +197,10 @@ if "$cygwin" || "$msys" ; then
|
|||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
# Collect all arguments for the java command;
|
# Collect all arguments for the java command;
|
||||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||||
# shell script including quotes and variable substitutions, so put them in
|
# shell script including quotes and variable substitutions, so put them in
|
||||||
@@ -205,6 +213,12 @@ set -- \
|
|||||||
org.gradle.wrapper.GradleWrapperMain \
|
org.gradle.wrapper.GradleWrapperMain \
|
||||||
"$@"
|
"$@"
|
||||||
|
|
||||||
|
# Stop when "xargs" is not available.
|
||||||
|
if ! command -v xargs >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "xargs is not available"
|
||||||
|
fi
|
||||||
|
|
||||||
# Use "xargs" to parse quoted args.
|
# Use "xargs" to parse quoted args.
|
||||||
#
|
#
|
||||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||||
|
|||||||
15
gradlew.bat
vendored
15
gradlew.bat
vendored
@@ -14,7 +14,7 @@
|
|||||||
@rem limitations under the License.
|
@rem limitations under the License.
|
||||||
@rem
|
@rem
|
||||||
|
|
||||||
@if "%DEBUG%" == "" @echo off
|
@if "%DEBUG%"=="" @echo off
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
@rem
|
@rem
|
||||||
@rem Gradle startup script for Windows
|
@rem Gradle startup script for Windows
|
||||||
@@ -25,7 +25,8 @@
|
|||||||
if "%OS%"=="Windows_NT" setlocal
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
set DIRNAME=%~dp0
|
set DIRNAME=%~dp0
|
||||||
if "%DIRNAME%" == "" set DIRNAME=.
|
if "%DIRNAME%"=="" set DIRNAME=.
|
||||||
|
@rem This is normally unused
|
||||||
set APP_BASE_NAME=%~n0
|
set APP_BASE_NAME=%~n0
|
||||||
set APP_HOME=%DIRNAME%
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
@@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
|||||||
|
|
||||||
set JAVA_EXE=java.exe
|
set JAVA_EXE=java.exe
|
||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if "%ERRORLEVEL%" == "0" goto execute
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
echo.
|
echo.
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
@@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
|||||||
|
|
||||||
:end
|
:end
|
||||||
@rem End local scope for the variables with windows NT shell
|
@rem End local scope for the variables with windows NT shell
|
||||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||||
|
|
||||||
:fail
|
:fail
|
||||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
rem the _cmd.exe /c_ return code!
|
rem the _cmd.exe /c_ return code!
|
||||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
set EXIT_CODE=%ERRORLEVEL%
|
||||||
exit /b 1
|
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||||
|
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||||
|
exit /b %EXIT_CODE%
|
||||||
|
|
||||||
:mainEnd
|
:mainEnd
|
||||||
if "%OS%"=="Windows_NT" endlocal
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|||||||
@@ -4,17 +4,19 @@ import app.revanced.patcher.data.Context
|
|||||||
import app.revanced.patcher.extensions.PatchExtensions.dependencies
|
import app.revanced.patcher.extensions.PatchExtensions.dependencies
|
||||||
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
||||||
import app.revanced.patcher.extensions.PatchExtensions.requiresIntegrations
|
import app.revanced.patcher.extensions.PatchExtensions.requiresIntegrations
|
||||||
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint.Companion.resolve
|
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
||||||
|
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint.Companion.resolveUsingLookupMap
|
||||||
import app.revanced.patcher.patch.*
|
import app.revanced.patcher.patch.*
|
||||||
import app.revanced.patcher.util.VersionReader
|
import brut.androlib.AaptInvoker
|
||||||
import brut.androlib.Androlib
|
import brut.androlib.ApkDecoder
|
||||||
import brut.androlib.meta.UsesFramework
|
import brut.androlib.Config
|
||||||
import brut.androlib.options.BuildOptions
|
import brut.androlib.res.Framework
|
||||||
import brut.androlib.res.AndrolibResources
|
import brut.androlib.res.ResourcesDecoder
|
||||||
import brut.androlib.res.data.ResPackage
|
import brut.androlib.res.decoder.AndroidManifestResourceParser
|
||||||
import brut.androlib.res.decoder.AXmlResourceParser
|
|
||||||
import brut.androlib.res.decoder.ResAttrDecoder
|
import brut.androlib.res.decoder.ResAttrDecoder
|
||||||
import brut.androlib.res.decoder.XmlPullStreamDecoder
|
import brut.androlib.res.decoder.XmlPullStreamDecoder
|
||||||
|
import brut.androlib.res.util.ExtMXSerializer
|
||||||
|
import brut.androlib.res.util.ExtXmlSerializer
|
||||||
import brut.androlib.res.xml.ResXmlPatcher
|
import brut.androlib.res.xml.ResXmlPatcher
|
||||||
import brut.directory.ExtFile
|
import brut.directory.ExtFile
|
||||||
import lanchon.multidexlib2.BasicDexFileNamer
|
import lanchon.multidexlib2.BasicDexFileNamer
|
||||||
@@ -41,14 +43,9 @@ class Patcher(private val options: PatcherOptions) {
|
|||||||
private var mergeIntegrations = false
|
private var mergeIntegrations = false
|
||||||
val context: PatcherContext
|
val context: PatcherContext
|
||||||
|
|
||||||
companion object {
|
private val config = Config.getDefaultConfig().apply {
|
||||||
@JvmStatic
|
aaptPath = options.aaptPath
|
||||||
val version = VersionReader.read()
|
frameworkDirectory = options.frameworkDirectory
|
||||||
private fun BuildOptions.setBuildOptions(options: PatcherOptions) {
|
|
||||||
this.aaptPath = options.aaptPath
|
|
||||||
this.useAapt2 = true
|
|
||||||
this.frameworkFolderLocation = options.frameworkFolderLocation
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@@ -85,55 +82,39 @@ class Patcher(private val options: PatcherOptions) {
|
|||||||
* Save the patched dex file.
|
* Save the patched dex file.
|
||||||
*/
|
*/
|
||||||
fun save(): PatcherResult {
|
fun save(): PatcherResult {
|
||||||
val packageMetadata = context.packageMetadata
|
|
||||||
val metaInfo = packageMetadata.metaInfo
|
|
||||||
var resourceFile: File? = null
|
var resourceFile: File? = null
|
||||||
|
|
||||||
when (resourceDecodingMode) {
|
when (resourceDecodingMode) {
|
||||||
ResourceDecodingMode.FULL -> {
|
ResourceDecodingMode.FULL -> {
|
||||||
|
logger.info("Compiling resources")
|
||||||
|
|
||||||
val cacheDirectory = ExtFile(options.resourceCacheDirectory)
|
val cacheDirectory = ExtFile(options.resourceCacheDirectory)
|
||||||
|
val aaptFile = cacheDirectory.resolve("aapt_temp_file").also {
|
||||||
|
Files.deleteIfExists(it.toPath())
|
||||||
|
}.also { resourceFile = it }
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val androlibResources = AndrolibResources().also { resources ->
|
AaptInvoker(
|
||||||
resources.buildOptions = BuildOptions().also { buildOptions ->
|
config,
|
||||||
buildOptions.setBuildOptions(options)
|
context.packageMetadata.apkInfo
|
||||||
buildOptions.isFramework = metaInfo.isFrameworkApk
|
).invokeAapt(
|
||||||
buildOptions.resourcesAreCompressed = metaInfo.compressionType
|
aaptFile,
|
||||||
buildOptions.doNotCompress = metaInfo.doNotCompress
|
cacheDirectory.resolve("AndroidManifest.xml").also {
|
||||||
|
ResXmlPatcher.fixingPublicAttrsInProviderAttributes(it)
|
||||||
|
},
|
||||||
|
cacheDirectory.resolve("res"),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
context.packageMetadata.apkInfo.usesFramework.let { usesFramework ->
|
||||||
|
usesFramework.ids.map { id ->
|
||||||
|
Framework(config).getFrameworkApk(id, usesFramework.tag)
|
||||||
|
}.toTypedArray()
|
||||||
}
|
}
|
||||||
|
|
||||||
resources.setSdkInfo(metaInfo.sdkInfo)
|
|
||||||
resources.setVersionInfo(metaInfo.versionInfo)
|
|
||||||
resources.setSharedLibrary(metaInfo.sharedLibrary)
|
|
||||||
resources.setSparseResources(metaInfo.sparseResources)
|
|
||||||
}
|
|
||||||
|
|
||||||
val manifestFile = cacheDirectory.resolve("AndroidManifest.xml")
|
|
||||||
|
|
||||||
ResXmlPatcher.fixingPublicAttrsInProviderAttributes(manifestFile)
|
|
||||||
|
|
||||||
val aaptFile = cacheDirectory.resolve("aapt_temp_file")
|
|
||||||
|
|
||||||
// delete if it exists
|
|
||||||
Files.deleteIfExists(aaptFile.toPath())
|
|
||||||
|
|
||||||
val resDirectory = cacheDirectory.resolve("res")
|
|
||||||
val includedFiles = metaInfo.usesFramework.ids.map { id ->
|
|
||||||
androlibResources.getFrameworkApk(
|
|
||||||
id, metaInfo.usesFramework.tag
|
|
||||||
)
|
|
||||||
}.toTypedArray()
|
|
||||||
|
|
||||||
logger.info("Compiling resources")
|
|
||||||
androlibResources.aaptPackage(
|
|
||||||
aaptFile, manifestFile, resDirectory, null, null, includedFiles
|
|
||||||
)
|
)
|
||||||
|
|
||||||
resourceFile = aaptFile
|
|
||||||
} finally {
|
} finally {
|
||||||
cacheDirectory.close()
|
cacheDirectory.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> logger.info("Not compiling resources because resource patching is not required")
|
else -> logger.info("Not compiling resources because resource patching is not required")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,7 +136,7 @@ class Patcher(private val options: PatcherOptions) {
|
|||||||
dexFiles.map {
|
dexFiles.map {
|
||||||
app.revanced.patcher.util.dex.DexFile(it.key, it.value.readAt(0))
|
app.revanced.patcher.util.dex.DexFile(it.key, it.value.readAt(0))
|
||||||
},
|
},
|
||||||
metaInfo.doNotCompress?.toList(),
|
context.packageMetadata.apkInfo.doNotCompress?.toList(),
|
||||||
resourceFile
|
resourceFile
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -199,72 +180,68 @@ class Patcher(private val options: PatcherOptions) {
|
|||||||
private fun decodeResources(mode: ResourceDecodingMode) {
|
private fun decodeResources(mode: ResourceDecodingMode) {
|
||||||
val extInputFile = ExtFile(options.inputFile)
|
val extInputFile = ExtFile(options.inputFile)
|
||||||
try {
|
try {
|
||||||
val androlib = Androlib(BuildOptions().also { it.setBuildOptions(options) })
|
val resourcesDecoder = ResourcesDecoder(config, extInputFile)
|
||||||
val resourceTable = androlib.getResTable(extInputFile, true)
|
|
||||||
when (mode) {
|
when (mode) {
|
||||||
ResourceDecodingMode.FULL -> {
|
ResourceDecodingMode.FULL -> {
|
||||||
val outDir = File(options.resourceCacheDirectory)
|
val outDir = File(options.resourceCacheDirectory)
|
||||||
if (outDir.exists()) {
|
if (outDir.exists()) {
|
||||||
logger.info("Deleting existing resource cache directory")
|
logger.info("Deleting existing resource cache directory")
|
||||||
if (!outDir.deleteRecursively()) {
|
if (!outDir.deleteRecursively()) logger.error("Failed to delete existing resource cache directory")
|
||||||
logger.error("Failed to delete existing resource cache directory")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
outDir.mkdirs()
|
outDir.mkdirs()
|
||||||
|
|
||||||
logger.info("Decoding resources")
|
logger.info("Decoding resources")
|
||||||
|
|
||||||
// decode resources to cache directory
|
resourcesDecoder.decodeManifest(outDir)
|
||||||
androlib.decodeManifestWithResources(extInputFile, outDir, resourceTable)
|
resourcesDecoder.decodeResources(outDir)
|
||||||
androlib.decodeResourcesFull(extInputFile, outDir, resourceTable)
|
|
||||||
|
|
||||||
// read additional metadata from the resource table
|
|
||||||
context.packageMetadata.let { metadata ->
|
|
||||||
metadata.metaInfo.usesFramework = UsesFramework().also { framework ->
|
|
||||||
framework.ids = resourceTable.listFramePackages().map { it.id }.sorted()
|
|
||||||
}
|
|
||||||
|
|
||||||
// read files to not compress
|
|
||||||
metadata.metaInfo.doNotCompress = buildList {
|
|
||||||
androlib.recordUncompressedFiles(extInputFile, this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
context.packageMetadata.also {
|
||||||
|
it.apkInfo = resourcesDecoder.apkInfo
|
||||||
|
}.apkInfo.doNotCompress = ApkDecoder(config, extInputFile).recordUncompressedFiles(
|
||||||
|
context.packageMetadata.apkInfo, resourcesDecoder.resFileMapping
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
ResourceDecodingMode.MANIFEST_ONLY -> {
|
ResourceDecodingMode.MANIFEST_ONLY -> {
|
||||||
logger.info("Decoding AndroidManifest.xml only, because resources are not needed")
|
logger.info("Decoding AndroidManifest.xml only, because resources are not needed")
|
||||||
|
|
||||||
// create decoder for the resource table
|
// Instead of using resourceDecoder.decodeManifest which decodes the whole file
|
||||||
val decoder = ResAttrDecoder()
|
// use the XmlPullStreamDecoder in order to get necessary information from the manifest
|
||||||
decoder.currentPackage = ResPackage(resourceTable, 0, null)
|
// used below.
|
||||||
|
XmlPullStreamDecoder(AndroidManifestResourceParser().apply {
|
||||||
// create xml parser with the decoder
|
attrDecoder = ResAttrDecoder().apply { this.resTable = resourcesDecoder.resTable }
|
||||||
val axmlParser = AXmlResourceParser()
|
}, ExtMXSerializer().apply {
|
||||||
axmlParser.attrDecoder = decoder
|
setProperty(
|
||||||
|
ExtXmlSerializer.PROPERTY_SERIALIZER_INDENTATION, " "
|
||||||
// parse package information with the decoder and parser which will set required values in the resource table
|
)
|
||||||
// instead of decodeManifest another more low level solution can be created to make it faster/better
|
setProperty(
|
||||||
XmlPullStreamDecoder(
|
ExtXmlSerializer.PROPERTY_SERIALIZER_LINE_SEPARATOR,
|
||||||
axmlParser, AndrolibResources().resXmlSerializer
|
System.getProperty("line.separator")
|
||||||
|
)
|
||||||
|
setProperty(
|
||||||
|
ExtXmlSerializer.PROPERTY_DEFAULT_ENCODING,
|
||||||
|
"utf-8"
|
||||||
|
)
|
||||||
|
setDisabledAttrEscape(true)
|
||||||
|
}
|
||||||
).decodeManifest(
|
).decodeManifest(
|
||||||
extInputFile.directory.getFileInput("AndroidManifest.xml"),
|
extInputFile.directory.getFileInput("AndroidManifest.xml"),
|
||||||
// Older Android versions do not support OutputStream.nullOutputStream()
|
// Older Android versions do not support OutputStream.nullOutputStream()
|
||||||
object : OutputStream() {
|
object : OutputStream() { override fun write(b: Int) { /* do nothing */ } }
|
||||||
override fun write(b: Int) {
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// read of the resourceTable which is created by reading the manifest file
|
// Get the package name and version from the manifest using the XmlPullStreamDecoder.
|
||||||
|
// XmlPullStreamDecoder.decodeManifest() sets metadata.apkInfo.
|
||||||
context.packageMetadata.let { metadata ->
|
context.packageMetadata.let { metadata ->
|
||||||
metadata.packageName = resourceTable.currentResPackage.name
|
metadata.apkInfo = resourcesDecoder.apkInfo
|
||||||
metadata.packageVersion = resourceTable.versionInfo.versionName ?: resourceTable.versionInfo.versionCode
|
|
||||||
metadata.metaInfo.versionInfo = resourceTable.versionInfo
|
metadata.packageName = resourcesDecoder.resTable.currentResPackage.name
|
||||||
metadata.metaInfo.sdkInfo = resourceTable.sdkInfo
|
resourcesDecoder.apkInfo.versionInfo.let {
|
||||||
|
metadata.packageVersion = it.versionName ?: it.versionCode
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
extInputFile.close()
|
extInputFile.close()
|
||||||
@@ -322,10 +299,7 @@ class Patcher(private val options: PatcherOptions) {
|
|||||||
context.resourceContext
|
context.resourceContext
|
||||||
} else {
|
} else {
|
||||||
context.bytecodeContext.also { context ->
|
context.bytecodeContext.also { context ->
|
||||||
(patchInstance as BytecodePatch).fingerprints?.resolve(
|
(patchInstance as BytecodePatch).fingerprints?.resolveUsingLookupMap(context)
|
||||||
context,
|
|
||||||
context.classes.classes
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -345,14 +319,17 @@ class Patcher(private val options: PatcherOptions) {
|
|||||||
return sequence {
|
return sequence {
|
||||||
if (mergeIntegrations) context.integrations.merge(logger)
|
if (mergeIntegrations) context.integrations.merge(logger)
|
||||||
|
|
||||||
|
logger.trace("Initialize lookup maps for method MethodFingerprint resolution")
|
||||||
|
|
||||||
|
MethodFingerprint.initializeFingerprintResolutionLookupMaps(context.bytecodeContext)
|
||||||
|
|
||||||
// prevent from decoding the manifest twice if it is not needed
|
// prevent from decoding the manifest twice if it is not needed
|
||||||
if (resourceDecodingMode == ResourceDecodingMode.FULL) decodeResources(ResourceDecodingMode.FULL)
|
if (resourceDecodingMode == ResourceDecodingMode.FULL) decodeResources(ResourceDecodingMode.FULL)
|
||||||
|
|
||||||
logger.trace("Executing all patches")
|
logger.info("Executing patches")
|
||||||
|
|
||||||
val executedPatches = LinkedHashMap<String, ExecutedPatch>() // first is name
|
val executedPatches = LinkedHashMap<String, ExecutedPatch>() // first is name
|
||||||
|
|
||||||
|
|
||||||
context.patches.forEach { patch ->
|
context.patches.forEach { patch ->
|
||||||
val patchResult = executePatch(patch, executedPatches)
|
val patchResult = executePatch(patch, executedPatches)
|
||||||
|
|
||||||
@@ -388,6 +365,8 @@ class Patcher(private val options: PatcherOptions) {
|
|||||||
if (stopOnError) return@sequence
|
if (stopOnError) return@sequence
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MethodFingerprint.clearFingerprintResolutionLookupMaps()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,13 +9,13 @@ import java.io.File
|
|||||||
* @param inputFile The input file (usually an apk file).
|
* @param inputFile The input file (usually an apk file).
|
||||||
* @param resourceCacheDirectory Directory to cache resources.
|
* @param resourceCacheDirectory Directory to cache resources.
|
||||||
* @param aaptPath Optional path to a custom aapt binary.
|
* @param aaptPath Optional path to a custom aapt binary.
|
||||||
* @param frameworkFolderLocation Optional path to a custom framework folder.
|
* @param frameworkDirectory Optional path to a custom framework directory.
|
||||||
* @param logger Custom logger implementation for the [Patcher].
|
* @param logger Custom logger implementation for the [Patcher].
|
||||||
*/
|
*/
|
||||||
data class PatcherOptions(
|
data class PatcherOptions(
|
||||||
internal val inputFile: File,
|
internal val inputFile: File,
|
||||||
internal val resourceCacheDirectory: String,
|
internal val resourceCacheDirectory: String,
|
||||||
internal val aaptPath: String? = null,
|
internal val aaptPath: String? = null,
|
||||||
internal val frameworkFolderLocation: String? = null,
|
internal val frameworkDirectory: String? = null,
|
||||||
internal val logger: Logger = NopLogger
|
internal val logger: Logger = NopLogger
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ annotation class Description(
|
|||||||
* @param version The version of a [Patch].
|
* @param version The version of a [Patch].
|
||||||
*/
|
*/
|
||||||
@Target(AnnotationTarget.CLASS)
|
@Target(AnnotationTarget.CLASS)
|
||||||
|
@Deprecated("This annotation is deprecated and will be removed in a future release.")
|
||||||
annotation class Version(
|
annotation class Version(
|
||||||
val version: String,
|
val version: String,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
package app.revanced.patcher.data
|
package app.revanced.patcher.data
|
||||||
|
|
||||||
import brut.androlib.meta.MetaInfo
|
import brut.androlib.apk.ApkInfo
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Metadata about a package.
|
* Metadata about a package.
|
||||||
*/
|
*/
|
||||||
class PackageMetadata {
|
class PackageMetadata {
|
||||||
lateinit var packageName: String
|
lateinit var packageName: String
|
||||||
|
internal set
|
||||||
lateinit var packageVersion: String
|
lateinit var packageVersion: String
|
||||||
|
internal set
|
||||||
|
|
||||||
internal val metaInfo: MetaInfo = MetaInfo()
|
internal lateinit var apkInfo: ApkInfo
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -321,6 +321,11 @@ object InstructionExtensions {
|
|||||||
* @param T The type of instruction to return.
|
* @param T The type of instruction to return.
|
||||||
* @return The instruction.
|
* @return The instruction.
|
||||||
*/
|
*/
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
fun <T> MutableMethod.getInstruction(index: Int): T = implementation!!.getInstruction<T>(index)
|
fun <T> MutableMethod.getInstruction(index: Int): T = implementation!!.getInstruction<T>(index)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the instructions of a method.
|
||||||
|
* @return The instructions.
|
||||||
|
*/
|
||||||
|
fun MutableMethod.getInstructions(): MutableList<BuilderInstruction> = implementation!!.instructions
|
||||||
}
|
}
|
||||||
@@ -25,6 +25,7 @@ object PatchExtensions {
|
|||||||
/**
|
/**
|
||||||
* The version of a [Patch].
|
* The version of a [Patch].
|
||||||
*/
|
*/
|
||||||
|
@Deprecated("This property is deprecated and will be removed in a future release.")
|
||||||
val Class<out Patch<Context>>.version
|
val Class<out Patch<Context>>.version
|
||||||
get() = findAnnotationRecursively(Version::class)?.version
|
get() = findAnnotationRecursively(Version::class)?.version
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ import app.revanced.patcher.data.BytecodeContext
|
|||||||
import app.revanced.patcher.extensions.MethodFingerprintExtensions.fuzzyPatternScanMethod
|
import app.revanced.patcher.extensions.MethodFingerprintExtensions.fuzzyPatternScanMethod
|
||||||
import app.revanced.patcher.fingerprint.Fingerprint
|
import app.revanced.patcher.fingerprint.Fingerprint
|
||||||
import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
|
import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
|
||||||
|
import app.revanced.patcher.patch.PatchResultError
|
||||||
import app.revanced.patcher.util.proxy.ClassProxy
|
import app.revanced.patcher.util.proxy.ClassProxy
|
||||||
|
import org.jf.dexlib2.AccessFlags
|
||||||
import org.jf.dexlib2.Opcode
|
import org.jf.dexlib2.Opcode
|
||||||
import org.jf.dexlib2.iface.ClassDef
|
import org.jf.dexlib2.iface.ClassDef
|
||||||
import org.jf.dexlib2.iface.Method
|
import org.jf.dexlib2.iface.Method
|
||||||
@@ -12,19 +14,21 @@ import org.jf.dexlib2.iface.instruction.Instruction
|
|||||||
import org.jf.dexlib2.iface.instruction.ReferenceInstruction
|
import org.jf.dexlib2.iface.instruction.ReferenceInstruction
|
||||||
import org.jf.dexlib2.iface.reference.StringReference
|
import org.jf.dexlib2.iface.reference.StringReference
|
||||||
import org.jf.dexlib2.util.MethodUtil
|
import org.jf.dexlib2.util.MethodUtil
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
private typealias StringMatch = MethodFingerprintResult.MethodFingerprintScanResult.StringsScanResult.StringMatch
|
private typealias StringMatch = MethodFingerprintResult.MethodFingerprintScanResult.StringsScanResult.StringMatch
|
||||||
private typealias StringsScanResult = MethodFingerprintResult.MethodFingerprintScanResult.StringsScanResult
|
private typealias StringsScanResult = MethodFingerprintResult.MethodFingerprintScanResult.StringsScanResult
|
||||||
|
private typealias MethodClassPair = Pair<Method, ClassDef>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the [MethodFingerprint] for a method.
|
* A fingerprint to resolve methods.
|
||||||
* @param returnType The return type of the method.
|
*
|
||||||
* @param accessFlags The access flags of the method.
|
* @param returnType The method's return type compared using [String.startsWith].
|
||||||
* @param parameters The parameters of the method.
|
* @param accessFlags The method's exact access flags using values of [AccessFlags].
|
||||||
* @param opcodes The list of opcodes of the method.
|
* @param parameters The parameters of the method. Partial matches allowed and follow the same rules as [returnType].
|
||||||
* @param strings A list of strings which a method contains.
|
* @param opcodes An opcode pattern of the method's instructions. Wildcard or unknown opcodes can be specified by `null`.
|
||||||
|
* @param strings A list of the method's strings compared each using [String.contains].
|
||||||
* @param customFingerprint A custom condition for this fingerprint.
|
* @param customFingerprint A custom condition for this fingerprint.
|
||||||
* A `null` opcode is equals to an unknown opcode.
|
|
||||||
*/
|
*/
|
||||||
abstract class MethodFingerprint(
|
abstract class MethodFingerprint(
|
||||||
internal val returnType: String? = null,
|
internal val returnType: String? = null,
|
||||||
@@ -40,6 +44,201 @@ abstract class MethodFingerprint(
|
|||||||
var result: MethodFingerprintResult? = null
|
var result: MethodFingerprintResult? = null
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
/**
|
||||||
|
* A list of methods and the class they were found in.
|
||||||
|
*/
|
||||||
|
private val methods = mutableListOf<MethodClassPair>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lookup map for methods keyed to the methods access flags, return type and parameter.
|
||||||
|
*/
|
||||||
|
private val methodSignatureLookupMap = mutableMapOf<String, MutableList<MethodClassPair>>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lookup map for methods keyed to the strings contained in the method.
|
||||||
|
*/
|
||||||
|
private val methodStringsLookupMap = mutableMapOf<String, MutableList<MethodClassPair>>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends a string based on the parameter reference types of this method.
|
||||||
|
*/
|
||||||
|
private fun StringBuilder.appendParameters(parameters: Iterable<CharSequence>) {
|
||||||
|
// Maximum parameters to use in the signature key.
|
||||||
|
// Some apps have methods with an incredible number of parameters (over 100 parameters have been seen).
|
||||||
|
// To keep the signature map from becoming needlessly bloated,
|
||||||
|
// group together in the same map entry all methods with the same access/return and 5 or more parameters.
|
||||||
|
// The value of 5 was chosen based on local performance testing and is not set in stone.
|
||||||
|
val maxSignatureParameters = 5
|
||||||
|
// Must append a unique value before the parameters to distinguish this key includes the parameters.
|
||||||
|
// If this is not appended, then methods with no parameters
|
||||||
|
// will collide with different keys that specify access/return but omit the parameters.
|
||||||
|
append("p:")
|
||||||
|
parameters.forEachIndexed { index, parameter ->
|
||||||
|
if (index >= maxSignatureParameters) return
|
||||||
|
append(parameter.first())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes lookup maps for [MethodFingerprint] resolution
|
||||||
|
* using attributes of methods such as the method signature or strings.
|
||||||
|
*
|
||||||
|
* @param context The [BytecodeContext] containing the classes to initialize the lookup maps with.
|
||||||
|
*/
|
||||||
|
internal fun initializeFingerprintResolutionLookupMaps(context: BytecodeContext) {
|
||||||
|
fun MutableMap<String, MutableList<MethodClassPair>>.add(
|
||||||
|
key: String,
|
||||||
|
methodClassPair: MethodClassPair
|
||||||
|
) {
|
||||||
|
var methodClassPairs = this[key]
|
||||||
|
|
||||||
|
methodClassPairs ?: run {
|
||||||
|
methodClassPairs = LinkedList<MethodClassPair>().also { this[key] = it }
|
||||||
|
}
|
||||||
|
|
||||||
|
methodClassPairs!!.add(methodClassPair)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (methods.isNotEmpty()) throw PatchResultError("Map already initialized")
|
||||||
|
|
||||||
|
context.classes.classes.forEach { classDef ->
|
||||||
|
classDef.methods.forEach { method ->
|
||||||
|
val methodClassPair = method to classDef
|
||||||
|
|
||||||
|
// For fingerprints with no access or return type specified.
|
||||||
|
methods += methodClassPair
|
||||||
|
|
||||||
|
val accessFlagsReturnKey = method.accessFlags.toString() + method.returnType.first()
|
||||||
|
|
||||||
|
// Add <access><returnType> as the key.
|
||||||
|
methodSignatureLookupMap.add(accessFlagsReturnKey, methodClassPair)
|
||||||
|
|
||||||
|
// Add <access><returnType>[parameters] as the key.
|
||||||
|
methodSignatureLookupMap.add(
|
||||||
|
buildString {
|
||||||
|
append(accessFlagsReturnKey)
|
||||||
|
appendParameters(method.parameterTypes)
|
||||||
|
},
|
||||||
|
methodClassPair
|
||||||
|
)
|
||||||
|
|
||||||
|
// Add strings contained in the method as the key.
|
||||||
|
method.implementation?.instructions?.forEach instructions@{ instruction ->
|
||||||
|
if (instruction.opcode != Opcode.CONST_STRING && instruction.opcode != Opcode.CONST_STRING_JUMBO)
|
||||||
|
return@instructions
|
||||||
|
|
||||||
|
val string = ((instruction as ReferenceInstruction).reference as StringReference).string
|
||||||
|
|
||||||
|
methodStringsLookupMap.add(string, methodClassPair)
|
||||||
|
}
|
||||||
|
|
||||||
|
// In the future, the class type could be added to the lookup map.
|
||||||
|
// This would require MethodFingerprint to be changed to include the class type.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the internal lookup maps created in [initializeFingerprintResolutionLookupMaps]
|
||||||
|
*/
|
||||||
|
internal fun clearFingerprintResolutionLookupMaps() {
|
||||||
|
methods.clear()
|
||||||
|
methodSignatureLookupMap.clear()
|
||||||
|
methodStringsLookupMap.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a list of [MethodFingerprint] using the lookup map built by [initializeFingerprintResolutionLookupMaps].
|
||||||
|
*
|
||||||
|
* [MethodFingerprint] resolution is fast, but if many are present they can consume a noticeable
|
||||||
|
* amount of time because they are resolved in sequence.
|
||||||
|
*
|
||||||
|
* For apps with many fingerprints, resolving performance can be improved by:
|
||||||
|
* - Slowest: Specify [opcodes] and nothing else.
|
||||||
|
* - Fast: Specify [accessFlags], [returnType].
|
||||||
|
* - Faster: Specify [accessFlags], [returnType] and [parameters].
|
||||||
|
* - Fastest: Specify [strings], with at least one string being an exact (non-partial) match.
|
||||||
|
*/
|
||||||
|
internal fun Iterable<MethodFingerprint>.resolveUsingLookupMap(context: BytecodeContext) {
|
||||||
|
if (methods.isEmpty()) throw PatchResultError("lookup map not initialized")
|
||||||
|
|
||||||
|
for (fingerprint in this) {
|
||||||
|
fingerprint.resolveUsingLookupMap(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a [MethodFingerprint] using the lookup map built by [initializeFingerprintResolutionLookupMaps].
|
||||||
|
*
|
||||||
|
* [MethodFingerprint] resolution is fast, but if many are present they can consume a noticeable
|
||||||
|
* amount of time because they are resolved in sequence.
|
||||||
|
*
|
||||||
|
* For apps with many fingerprints, resolving performance can be improved by:
|
||||||
|
* - Slowest: Specify [opcodes] and nothing else.
|
||||||
|
* - Fast: Specify [accessFlags], [returnType].
|
||||||
|
* - Faster: Specify [accessFlags], [returnType] and [parameters].
|
||||||
|
* - Fastest: Specify [strings], with at least one string being an exact (non-partial) match.
|
||||||
|
*/
|
||||||
|
internal fun MethodFingerprint.resolveUsingLookupMap(context: BytecodeContext): Boolean {
|
||||||
|
/**
|
||||||
|
* Lookup [MethodClassPair]s that match the methods strings present in a [MethodFingerprint].
|
||||||
|
*
|
||||||
|
* @return A list of [MethodClassPair]s that match the methods strings present in a [MethodFingerprint].
|
||||||
|
*/
|
||||||
|
fun MethodFingerprint.methodStringsLookup(): List<MethodClassPair>? {
|
||||||
|
strings?.forEach {
|
||||||
|
val methods = methodStringsLookupMap[it]
|
||||||
|
if (methods != null) return methods
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lookup [MethodClassPair]s that match the method signature present in a [MethodFingerprint].
|
||||||
|
*
|
||||||
|
* @return A list of [MethodClassPair]s that match the method signature present in a [MethodFingerprint].
|
||||||
|
*/
|
||||||
|
fun MethodFingerprint.methodSignatureLookup(): List<MethodClassPair> {
|
||||||
|
if (accessFlags == null) return methods
|
||||||
|
|
||||||
|
var returnTypeValue = returnType
|
||||||
|
if (returnTypeValue == null) {
|
||||||
|
if (AccessFlags.CONSTRUCTOR.isSet(accessFlags)) {
|
||||||
|
// Constructors always have void return type
|
||||||
|
returnTypeValue = "V"
|
||||||
|
} else {
|
||||||
|
return methods
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val key = buildString {
|
||||||
|
append(accessFlags)
|
||||||
|
append(returnTypeValue.first())
|
||||||
|
if (parameters != null) appendParameters(parameters)
|
||||||
|
}
|
||||||
|
return methodSignatureLookupMap[key] ?: return emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a [MethodFingerprint] using a list of [MethodClassPair].
|
||||||
|
*
|
||||||
|
* @return True if the resolution was successful, false otherwise.
|
||||||
|
*/
|
||||||
|
fun MethodFingerprint.resolveUsingMethodClassPair(classMethods: Iterable<MethodClassPair>): Boolean {
|
||||||
|
classMethods.forEach { classAndMethod ->
|
||||||
|
if (resolve(context, classAndMethod.first, classAndMethod.second)) return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
val methodsWithSameStrings = methodStringsLookup()
|
||||||
|
if (methodsWithSameStrings != null) if (resolveUsingMethodClassPair(methodsWithSameStrings)) return true
|
||||||
|
|
||||||
|
// No strings declared or none matched (partial matches are allowed).
|
||||||
|
// Use signature matching.
|
||||||
|
return resolveUsingMethodClassPair(methodSignatureLookup())
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve a list of [MethodFingerprint] against a list of [ClassDef].
|
* Resolve a list of [MethodFingerprint] against a list of [ClassDef].
|
||||||
*
|
*
|
||||||
@@ -48,10 +247,10 @@ abstract class MethodFingerprint(
|
|||||||
* @return True if the resolution was successful, false otherwise.
|
* @return True if the resolution was successful, false otherwise.
|
||||||
*/
|
*/
|
||||||
fun Iterable<MethodFingerprint>.resolve(context: BytecodeContext, classes: Iterable<ClassDef>) {
|
fun Iterable<MethodFingerprint>.resolve(context: BytecodeContext, classes: Iterable<ClassDef>) {
|
||||||
for (fingerprint in this) // For each fingerprint
|
for (fingerprint in this) // For each fingerprint...
|
||||||
classes@ for (classDef in classes) // search through all classes for the fingerprint
|
classes@ for (classDef in classes) // ...search through all classes for the MethodFingerprint
|
||||||
if (fingerprint.resolve(context, classDef))
|
if (fingerprint.resolve(context, classDef))
|
||||||
break@classes // if the resolution succeeded, continue with the next fingerprint
|
break@classes // ...if the resolution succeeded, continue with the next MethodFingerprint.
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -144,6 +343,52 @@ abstract class MethodFingerprint(
|
|||||||
val patternScanResult = if (methodFingerprint.opcodes != null) {
|
val patternScanResult = if (methodFingerprint.opcodes != null) {
|
||||||
method.implementation?.instructions ?: return false
|
method.implementation?.instructions ?: return false
|
||||||
|
|
||||||
|
fun Method.patternScan(
|
||||||
|
fingerprint: MethodFingerprint
|
||||||
|
): MethodFingerprintResult.MethodFingerprintScanResult.PatternScanResult? {
|
||||||
|
val instructions = this.implementation!!.instructions
|
||||||
|
val fingerprintFuzzyPatternScanThreshold = fingerprint.fuzzyPatternScanMethod?.threshold ?: 0
|
||||||
|
|
||||||
|
val pattern = fingerprint.opcodes!!
|
||||||
|
val instructionLength = instructions.count()
|
||||||
|
val patternLength = pattern.count()
|
||||||
|
|
||||||
|
for (index in 0 until instructionLength) {
|
||||||
|
var patternIndex = 0
|
||||||
|
var threshold = fingerprintFuzzyPatternScanThreshold
|
||||||
|
|
||||||
|
while (index + patternIndex < instructionLength) {
|
||||||
|
val originalOpcode = instructions.elementAt(index + patternIndex).opcode
|
||||||
|
val patternOpcode = pattern.elementAt(patternIndex)
|
||||||
|
|
||||||
|
if (patternOpcode != null && patternOpcode.ordinal != originalOpcode.ordinal) {
|
||||||
|
// reaching maximum threshold (0) means,
|
||||||
|
// the pattern does not match to the current instructions
|
||||||
|
if (threshold-- == 0) break
|
||||||
|
}
|
||||||
|
|
||||||
|
if (patternIndex < patternLength - 1) {
|
||||||
|
// if the entire pattern has not been scanned yet
|
||||||
|
// continue the scan
|
||||||
|
patternIndex++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// the pattern is valid, generate warnings if fuzzyPatternScanMethod is FuzzyPatternScanMethod
|
||||||
|
val result =
|
||||||
|
MethodFingerprintResult.MethodFingerprintScanResult.PatternScanResult(
|
||||||
|
index,
|
||||||
|
index + patternIndex
|
||||||
|
)
|
||||||
|
if (fingerprint.fuzzyPatternScanMethod !is FuzzyPatternScanMethod) return result
|
||||||
|
result.warnings = result.createWarnings(pattern, instructions)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
method.patternScan(methodFingerprint) ?: return false
|
method.patternScan(methodFingerprint) ?: return false
|
||||||
} else null
|
} else null
|
||||||
|
|
||||||
@@ -160,52 +405,6 @@ abstract class MethodFingerprint(
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Method.patternScan(
|
|
||||||
fingerprint: MethodFingerprint
|
|
||||||
): MethodFingerprintResult.MethodFingerprintScanResult.PatternScanResult? {
|
|
||||||
val instructions = this.implementation!!.instructions
|
|
||||||
val fingerprintFuzzyPatternScanThreshold = fingerprint.fuzzyPatternScanMethod?.threshold ?: 0
|
|
||||||
|
|
||||||
val pattern = fingerprint.opcodes!!
|
|
||||||
val instructionLength = instructions.count()
|
|
||||||
val patternLength = pattern.count()
|
|
||||||
|
|
||||||
for (index in 0 until instructionLength) {
|
|
||||||
var patternIndex = 0
|
|
||||||
var threshold = fingerprintFuzzyPatternScanThreshold
|
|
||||||
|
|
||||||
while (index + patternIndex < instructionLength) {
|
|
||||||
val originalOpcode = instructions.elementAt(index + patternIndex).opcode
|
|
||||||
val patternOpcode = pattern.elementAt(patternIndex)
|
|
||||||
|
|
||||||
if (patternOpcode != null && patternOpcode.ordinal != originalOpcode.ordinal) {
|
|
||||||
// reaching maximum threshold (0) means,
|
|
||||||
// the pattern does not match to the current instructions
|
|
||||||
if (threshold-- == 0) break
|
|
||||||
}
|
|
||||||
|
|
||||||
if (patternIndex < patternLength - 1) {
|
|
||||||
// if the entire pattern has not been scanned yet
|
|
||||||
// continue the scan
|
|
||||||
patternIndex++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// the pattern is valid, generate warnings if fuzzyPatternScanMethod is FuzzyPatternScanMethod
|
|
||||||
val result =
|
|
||||||
MethodFingerprintResult.MethodFingerprintScanResult.PatternScanResult(
|
|
||||||
index,
|
|
||||||
index + patternIndex
|
|
||||||
)
|
|
||||||
if (fingerprint.fuzzyPatternScanMethod !is FuzzyPatternScanMethod) return result
|
|
||||||
result.warnings = result.createWarnings(pattern, instructions)
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun MethodFingerprintResult.MethodFingerprintScanResult.PatternScanResult.createWarnings(
|
private fun MethodFingerprintResult.MethodFingerprintScanResult.PatternScanResult.createWarnings(
|
||||||
pattern: Iterable<Opcode?>, instructions: Iterable<Instruction>
|
pattern: Iterable<Opcode?>, instructions: Iterable<Instruction>
|
||||||
) = buildList {
|
) = buildList {
|
||||||
|
|||||||
@@ -229,20 +229,4 @@ sealed class PatchOption<T>(
|
|||||||
) : ListOption<Int>(
|
) : ListOption<Int>(
|
||||||
key, default, options, title, description, required, validator
|
key, default, options, title, description, required, validator
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
|
||||||
* A [PatchOption] representing a [Path], backed by a [String].
|
|
||||||
* The validator passes a [String], if you need a [Path] you will have to convert it yourself.
|
|
||||||
* @see PatchOption
|
|
||||||
*/
|
|
||||||
class PathOption(
|
|
||||||
key: String,
|
|
||||||
default: Path?,
|
|
||||||
title: String,
|
|
||||||
description: String,
|
|
||||||
required: Boolean = false,
|
|
||||||
validator: (String?) -> Boolean = { true }
|
|
||||||
) : PatchOption<String>(
|
|
||||||
key, default?.pathString, title, description, required, validator
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
package app.revanced.patcher.util
|
|
||||||
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
internal object VersionReader {
|
|
||||||
@JvmStatic
|
|
||||||
private val props = Properties().apply {
|
|
||||||
load(
|
|
||||||
VersionReader::class.java.getResourceAsStream("/revanced-patcher/version.properties")
|
|
||||||
?: throw IllegalStateException("Could not load version.properties")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun read(): String {
|
|
||||||
return props.getProperty("version") ?: throw IllegalStateException("Version not found")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
package app.revanced.patcher.util.patch
|
package app.revanced.patcher.util.patch
|
||||||
|
|
||||||
import app.revanced.patcher.data.Context
|
import app.revanced.patcher.data.Context
|
||||||
|
import app.revanced.patcher.extensions.AnnotationExtensions.findAnnotationRecursively
|
||||||
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
||||||
import app.revanced.patcher.patch.Patch
|
import app.revanced.patcher.patch.Patch
|
||||||
import org.jf.dexlib2.DexFileFactory
|
import org.jf.dexlib2.DexFileFactory
|
||||||
@@ -19,7 +20,13 @@ sealed class PatchBundle(path: String) : File(path) {
|
|||||||
internal fun loadPatches(classLoader: ClassLoader, classNames: Iterator<String>) = buildList {
|
internal fun loadPatches(classLoader: ClassLoader, classNames: Iterator<String>) = buildList {
|
||||||
classNames.forEach { className ->
|
classNames.forEach { className ->
|
||||||
val clazz = classLoader.loadClass(className)
|
val clazz = classLoader.loadClass(className)
|
||||||
if (!clazz.isAnnotationPresent(app.revanced.patcher.patch.annotations.Patch::class.java)) return@forEach
|
|
||||||
|
// Annotations can not Patch.
|
||||||
|
if (clazz.isAnnotation) return@forEach
|
||||||
|
|
||||||
|
clazz.findAnnotationRecursively(app.revanced.patcher.patch.annotations.Patch::class)
|
||||||
|
?: return@forEach
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST") this.add(clazz as Class<out Patch<Context>>)
|
@Suppress("UNCHECKED_CAST") this.add(clazz as Class<out Patch<Context>>)
|
||||||
}
|
}
|
||||||
}.sortedBy { it.patchName }
|
}.sortedBy { it.patchName }
|
||||||
@@ -43,11 +50,9 @@ sealed class PatchBundle(path: String) : File(path) {
|
|||||||
),
|
),
|
||||||
JarFile(this)
|
JarFile(this)
|
||||||
.stream()
|
.stream()
|
||||||
.filter {it.name.endsWith(".class") && !it.name.contains("$")}
|
.filter { it.name.endsWith(".class") && !it.name.contains("$") }
|
||||||
.map({it -> it.realName.replace('/', '.').replace(".class", "")}).iterator()
|
.map { it.realName.replace('/', '.').replace(".class", "") }.iterator()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -63,8 +68,9 @@ sealed class PatchBundle(path: String) : File(path) {
|
|||||||
* Patches will be loaded to the provided [dexClassLoader].
|
* Patches will be loaded to the provided [dexClassLoader].
|
||||||
*/
|
*/
|
||||||
fun loadPatches() = loadPatches(dexClassLoader,
|
fun loadPatches() = loadPatches(dexClassLoader,
|
||||||
DexFileFactory.loadDexFile(path, null).classes.asSequence().map({ classDef ->
|
DexFileFactory.loadDexFile(path, null).classes.asSequence().map { classDef ->
|
||||||
classDef.type.substring(1, classDef.length - 1).replace('/', '.')
|
classDef.type.substring(1, classDef.length - 1).replace('/', '.')
|
||||||
}).iterator())
|
}.iterator()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,8 +3,6 @@ package app.revanced.patcher.patch
|
|||||||
import app.revanced.patcher.usage.bytecode.ExampleBytecodePatch
|
import app.revanced.patcher.usage.bytecode.ExampleBytecodePatch
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import org.junit.jupiter.api.assertThrows
|
import org.junit.jupiter.api.assertThrows
|
||||||
import kotlin.io.path.Path
|
|
||||||
import kotlin.io.path.pathString
|
|
||||||
import kotlin.test.assertNotEquals
|
import kotlin.test.assertNotEquals
|
||||||
|
|
||||||
internal class PatchOptionsTest {
|
internal class PatchOptionsTest {
|
||||||
@@ -35,10 +33,6 @@ internal class PatchOptionsTest {
|
|||||||
println(choice)
|
println(choice)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is PatchOption.PathOption -> {
|
|
||||||
option.value = Path("test.txt").pathString
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val option = options.get<String>("key1")
|
val option = options.get<String>("key1")
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import app.revanced.patcher.annotation.Name
|
|||||||
import app.revanced.patcher.annotation.Version
|
import app.revanced.patcher.annotation.Version
|
||||||
import app.revanced.patcher.data.BytecodeContext
|
import app.revanced.patcher.data.BytecodeContext
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
|
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
|
||||||
import app.revanced.patcher.extensions.or
|
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||||
|
import app.revanced.patcher.extensions.or
|
||||||
import app.revanced.patcher.patch.*
|
import app.revanced.patcher.patch.*
|
||||||
import app.revanced.patcher.patch.annotations.DependsOn
|
import app.revanced.patcher.patch.annotations.DependsOn
|
||||||
import app.revanced.patcher.patch.annotations.Patch
|
import app.revanced.patcher.patch.annotations.Patch
|
||||||
@@ -29,7 +29,6 @@ import org.jf.dexlib2.immutable.reference.ImmutableFieldReference
|
|||||||
import org.jf.dexlib2.immutable.reference.ImmutableStringReference
|
import org.jf.dexlib2.immutable.reference.ImmutableStringReference
|
||||||
import org.jf.dexlib2.immutable.value.ImmutableFieldEncodedValue
|
import org.jf.dexlib2.immutable.value.ImmutableFieldEncodedValue
|
||||||
import org.jf.dexlib2.util.Preconditions
|
import org.jf.dexlib2.util.Preconditions
|
||||||
import kotlin.io.path.Path
|
|
||||||
|
|
||||||
@Patch
|
@Patch
|
||||||
@Name("example-bytecode-patch")
|
@Name("example-bytecode-patch")
|
||||||
@@ -193,10 +192,5 @@ class ExampleBytecodePatch : BytecodePatch(listOf(ExampleFingerprint)) {
|
|||||||
"key5", null, "title", "description", true
|
"key5", null, "title", "description", true
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
private var key6 by option(
|
|
||||||
PatchOption.PathOption(
|
|
||||||
"key6", Path("test.txt"), "title", "description", true
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user