Compare commits

..

29 Commits

Author SHA1 Message Date
semantic-release-bot
d971239997 chore(release): 1.0.0-dev.18 [skip ci]
# [1.0.0-dev.18](https://github.com/revanced/revanced-patcher/compare/v1.0.0-dev.17...v1.0.0-dev.18) (2022-06-04)

### Features

* `Dependencies` annotation ([83d608a](83d608ac06))
* optional `forStaticMethod` parameter for `InlineSmaliCompiler.compileMethodInstructions` ([28b9847](28b98478e4))
2022-06-04 00:28:58 +00:00
oSumAtrIX
28b98478e4 feat: optional forStaticMethod parameter for InlineSmaliCompiler.compileMethodInstructions 2022-06-04 02:25:13 +02:00
oSumAtrIX
83d608ac06 feat: Dependencies annotation 2022-06-03 17:49:31 +02:00
semantic-release-bot
b369a30dd5 chore(release): 1.0.0-dev.17 [skip ci]
# [1.0.0-dev.17](https://github.com/revanced/revanced-patcher/compare/v1.0.0-dev.16...v1.0.0-dev.17) (2022-05-31)

### Features

* patch dependencies annotation and `PatcherOptions` ([8442991](8442991290))
2022-05-31 23:41:12 +00:00
oSumAtrIX
8442991290 feat: patch dependencies annotation and PatcherOptions 2022-06-01 01:33:30 +02:00
semantic-release-bot
a06c0db6a7 chore(release): 1.0.0-dev.16 [skip ci]
# [1.0.0-dev.16](https://github.com/revanced/revanced-patcher/compare/v1.0.0-dev.15...v1.0.0-dev.16) (2022-05-27)

### Bug Fixes

* `JarPatchBundle` loading non-class files to class loader ([3f0c740](3f0c740200))
* remove dependency to fork of Apktool ([0fa529f](0fa529fcdf))

### Features

* migrate to `DexPatchBundle` and `JarPatchBundle` ([7573db2](7573db2575))
2022-05-27 12:30:13 +00:00
oSumAtrIX
3f0c740200 fix: JarPatchBundle loading non-class files to class loader 2022-05-27 14:26:06 +02:00
oSumAtrIX
545c5c144d chore: update gradlew wrapper 2022-05-26 03:52:28 +02:00
oSumAtrIX
0fa529fcdf fix: remove dependency to fork of Apktool 2022-05-26 03:51:25 +02:00
oSumAtrIX
7573db2575 feat: migrate to DexPatchBundle and JarPatchBundle
Signed-off-by: oSumAtrIX <johan.melkonyan1@web.de>
2022-05-26 01:02:41 +02:00
semantic-release-bot
70ca184cf9 chore(release): 1.0.0-dev.15 [skip ci]
# [1.0.0-dev.15](https://github.com/revanced/revanced-patcher/compare/v1.0.0-dev.14...v1.0.0-dev.15) (2022-05-25)

### Features

* utility functions to get metadata of patch & sigs ([72f16b7](72f16b7785))
2022-05-25 20:55:57 +00:00
Lucaskyy
72f16b7785 feat: utility functions to get metadata of patch & sigs 2022-05-25 22:54:20 +02:00
Lucaskyy
fc03639b26 chore: fix typo 2022-05-25 22:52:57 +02:00
semantic-release-bot
88a85f94e7 chore(release): 1.0.0-dev.14 [skip ci]
# [1.0.0-dev.14](https://github.com/revanced/revanced-patcher/compare/v1.0.0-dev.13...v1.0.0-dev.14) (2022-05-24)

### Bug Fixes

* reformat (trigger release) ([45a167e](45a167e785))
2022-05-24 18:12:32 +00:00
Lucaskyy
45a167e785 fix: reformat (trigger release) 2022-05-24 20:11:04 +02:00
Lucaskyy
699d8abf59 refactor: use apktool fork
also fixed some compilation issues
2022-05-24 17:43:43 +02:00
semantic-release-bot
b58c718699 chore(release): 1.0.0-dev.13 [skip ci]
# [1.0.0-dev.13](https://github.com/revanced/revanced-patcher/compare/v1.0.0-dev.12...v1.0.0-dev.13) (2022-05-24)

### Performance Improvements

* decode manifest only when not using resource patcher ([40b1fa4](40b1fa43e1))
2022-05-24 00:11:23 +00:00
oSumAtrIX
266d6810a9 refactor: use resourceData.get(path) instead of a reader/writer
Signed-off-by: oSumAtrIX <johan.melkonyan1@web.de>
2022-05-24 01:41:48 +02:00
oSumAtrIX
40b1fa43e1 perf: decode manifest only when not using resource patcher
Signed-off-by: oSumAtrIX <johan.melkonyan1@web.de>
2022-05-24 01:28:31 +02:00
oSumAtrIX
94f9594eed chore: update kotlin jvm
Signed-off-by: oSumAtrIX <johan.melkonyan1@web.de>
2022-05-24 00:16:57 +02:00
oSumAtrIX
cff58ab180 refactor: improve ExampleResourcePatch
Signed-off-by: oSumAtrIX <johan.melkonyan1@web.de>
2022-05-23 20:58:03 +02:00
oSumAtrIX
989646b0b5 chore: update dependencies
Signed-off-by: oSumAtrIX <johan.melkonyan1@web.de>
2022-05-23 20:56:17 +02:00
semantic-release-bot
5c3fbaee7a chore(release): 1.0.0-dev.12 [skip ci]
# [1.0.0-dev.12](https://github.com/revanced/revanced-patcher/compare/v1.0.0-dev.11...v1.0.0-dev.12) (2022-05-22)

### Bug Fixes

* using old instance of `Androlib` when saving ([5630e49](5630e49663))
2022-05-22 15:23:09 +00:00
oSumAtrIX
08525e9c26 Merge remote-tracking branch 'origin/dev' into dev 2022-05-22 17:21:50 +02:00
oSumAtrIX
5630e49663 fix: using old instance of Androlib when saving
Signed-off-by: oSumAtrIX <johan.melkonyan1@web.de>
2022-05-22 17:21:18 +02:00
semantic-release-bot
0543122427 chore(release): 1.0.0-dev.11 [skip ci]
# [1.0.0-dev.11](https://github.com/revanced/revanced-patcher/compare/v1.0.0-dev.10...v1.0.0-dev.11) (2022-05-22)

### Features

* `PatchLoader` ([1a99eca](1a99ecaffe))
* use annotations instead of metadata objects ([6726884](6726884be5))
2022-05-22 15:17:31 +00:00
oSumAtrIX
0873703056 Merge pull request #33 from revanced/annotations
feat: use annotations instead of metadata objects
2022-05-22 17:14:48 +02:00
oSumAtrIX
1a99ecaffe feat: PatchLoader
Signed-off-by: oSumAtrIX <johan.melkonyan1@web.de>
2022-05-22 17:12:46 +02:00
oSumAtrIX
6726884be5 feat: use annotations instead of metadata objects
Signed-off-by: oSumAtrIX <johan.melkonyan1@web.de>
2022-05-22 13:46:20 +02:00
71 changed files with 1042 additions and 683 deletions

View File

@@ -1,5 +1,6 @@
name: Release name: Release
on: on:
workflow_dispatch:
push: push:
branches: branches:
- main - main

View File

@@ -1,3 +1,67 @@
# [1.0.0-dev.18](https://github.com/revanced/revanced-patcher/compare/v1.0.0-dev.17...v1.0.0-dev.18) (2022-06-04)
### Features
* `Dependencies` annotation ([83d608a](https://github.com/revanced/revanced-patcher/commit/83d608ac06a7d5ceb31b6e0022b501d99edb63a3))
* optional `forStaticMethod` parameter for `InlineSmaliCompiler.compileMethodInstructions` ([28b9847](https://github.com/revanced/revanced-patcher/commit/28b98478e4e8e8f238e82f7fa2307aeb1547955d))
# [1.0.0-dev.17](https://github.com/revanced/revanced-patcher/compare/v1.0.0-dev.16...v1.0.0-dev.17) (2022-05-31)
### Features
* patch dependencies annotation and `PatcherOptions` ([8442991](https://github.com/revanced/revanced-patcher/commit/84429912900872405b44804943357dda8430a550))
# [1.0.0-dev.16](https://github.com/revanced/revanced-patcher/compare/v1.0.0-dev.15...v1.0.0-dev.16) (2022-05-27)
### Bug Fixes
* `JarPatchBundle` loading non-class files to class loader ([3f0c740](https://github.com/revanced/revanced-patcher/commit/3f0c740200dd91a060426638c2f8f516938b4c53))
* remove dependency to fork of Apktool ([0fa529f](https://github.com/revanced/revanced-patcher/commit/0fa529fcdf9a7b5ea9a361b9f9f32f3f3fce009f))
### Features
* migrate to `DexPatchBundle` and `JarPatchBundle` ([7573db2](https://github.com/revanced/revanced-patcher/commit/7573db25757de89824af4f3aea167e500120eabb))
# [1.0.0-dev.15](https://github.com/revanced/revanced-patcher/compare/v1.0.0-dev.14...v1.0.0-dev.15) (2022-05-25)
### Features
* utility functions to get metadata of patch & sigs ([72f16b7](https://github.com/revanced/revanced-patcher/commit/72f16b778587c28d8f8e91da502f197e7dc35d6d))
# [1.0.0-dev.14](https://github.com/revanced/revanced-patcher/compare/v1.0.0-dev.13...v1.0.0-dev.14) (2022-05-24)
### Bug Fixes
* reformat (trigger release) ([45a167e](https://github.com/revanced/revanced-patcher/commit/45a167e7856da0306f796953775c7b7543d9bec0))
# [1.0.0-dev.13](https://github.com/revanced/revanced-patcher/compare/v1.0.0-dev.12...v1.0.0-dev.13) (2022-05-24)
### Performance Improvements
* decode manifest only when not using resource patcher ([40b1fa4](https://github.com/revanced/revanced-patcher/commit/40b1fa43e1704ace29d3e349df2f4a8ea828c5c2))
# [1.0.0-dev.12](https://github.com/revanced/revanced-patcher/compare/v1.0.0-dev.11...v1.0.0-dev.12) (2022-05-22)
### Bug Fixes
* using old instance of `Androlib` when saving ([5630e49](https://github.com/revanced/revanced-patcher/commit/5630e4966310311cdfd53e2ba128255047626adc))
# [1.0.0-dev.11](https://github.com/revanced/revanced-patcher/compare/v1.0.0-dev.10...v1.0.0-dev.11) (2022-05-22)
### Features
* `PatchLoader` ([1a99eca](https://github.com/revanced/revanced-patcher/commit/1a99ecaffe5e55977655316e68b014fdeba374a1))
* use annotations instead of metadata objects ([6726884](https://github.com/revanced/revanced-patcher/commit/6726884be5af56b6856749e73fb9f4f97559854a))
# [1.0.0-dev.10](https://github.com/revanced/revanced-patcher/compare/v1.0.0-dev.9...v1.0.0-dev.10) (2022-05-07) # [1.0.0-dev.10](https://github.com/revanced/revanced-patcher/compare/v1.0.0-dev.9...v1.0.0-dev.10) (2022-05-07)

View File

@@ -1,5 +1,5 @@
plugins { plugins {
kotlin("jvm") version "1.6.20" kotlin("jvm") version "1.6.21"
java java
`maven-publish` `maven-publish`
} }
@@ -15,8 +15,10 @@ repositories {
// Instead, you should set them in: // Instead, you should set them in:
// Windows: %homepath%\.gradle\gradle.properties // Windows: %homepath%\.gradle\gradle.properties
// Linux: ~/.gradle/gradle.properties // Linux: ~/.gradle/gradle.properties
username = project.findProperty("gpr.user") as String? ?: System.getenv("GITHUB_ACTOR") // DO NOT CHANGE! username =
password = project.findProperty("gpr.key") as String? ?: System.getenv("GITHUB_TOKEN") // DO NOT CHANGE! project.findProperty("gpr.user") as String? ?: System.getenv("GITHUB_ACTOR") // DO NOT CHANGE!
password =
project.findProperty("gpr.key") as String? ?: System.getenv("GITHUB_TOKEN") // DO NOT CHANGE!
} }
} }
} }
@@ -24,11 +26,13 @@ repositories {
dependencies { dependencies {
implementation(kotlin("stdlib")) implementation(kotlin("stdlib"))
api("xpp3:xpp3:1.1.4c")
api("org.apktool:apktool-lib:2.6.1") api("org.apktool:apktool-lib:2.6.1")
api("app.revanced:multidexlib2:2.5.2.r2") api("app.revanced:multidexlib2:2.5.2.r2")
api("org.smali:smali:2.5.2") api("org.smali:smali:2.5.2")
testImplementation(kotlin("test")) testImplementation(kotlin("test"))
implementation(kotlin("reflect"))
} }
tasks { tasks {

View File

@@ -1,2 +1,2 @@
kotlin.code.style = official kotlin.code.style = official
version = 1.0.0-dev.10 version = 1.0.0-dev.18

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

269
gradlew vendored
View File

@@ -1,7 +1,7 @@
#!/usr/bin/env sh #!/bin/sh
# #
# Copyright 2015 the original author or authors. # Copyright <20> 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.
@@ -17,67 +17,101 @@
# #
############################################################################## ##############################################################################
## #
## Gradle start up script for UN*X # Gradle start up script for POSIX generated by Gradle.
## #
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions <20>$var<61>, <20>${var}<7D>, <20>${var:-default}<7D>, <20>${var+SET}<7D>,
# <20>${var#prefix}<7D>, <20>${var%suffix}<7D>, and <20>$( cmd )<29>;
# * compound commands having a testable exit status, especially <20>case<73>;
# * various built-in commands including <20>command<6E>, <20>set<65>, and <20>ulimit<69>.
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
############################################################################## ##############################################################################
# Attempt to set APP_HOME # Attempt to set APP_HOME
# Resolve links: $0 may be a link # Resolve links: $0 may be a link
PRG="$0" app_path=$0
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do # Need this for daisy-chained symlinks.
ls=`ls -ld "$PRG"` while
link=`expr "$ls" : '.*-> \(.*\)$'` APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
if expr "$link" : '/.*' > /dev/null; then [ -h "$app_path" ]
PRG="$link" do
else ls=$( ls -ld "$app_path" )
PRG=`dirname "$PRG"`"/$link" link=${ls#*' -> '}
fi case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle" APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"` APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. # 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"' 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
warn () { warn () {
echo "$*" echo "$*"
} } >&2
die () { die () {
echo echo
echo "$*" echo "$*"
echo echo
exit 1 exit 1
} } >&2
# OS specific support (must be 'true' or 'false'). # OS specific support (must be 'true' or 'false').
cygwin=false cygwin=false
msys=false msys=false
darwin=false darwin=false
nonstop=false nonstop=false
case "`uname`" in case "$( uname )" in #(
CYGWIN* ) CYGWIN* ) cygwin=true ;; #(
cygwin=true Darwin* ) darwin=true ;; #(
;; MSYS* | MINGW* ) msys=true ;; #(
Darwin* ) NONSTOP* ) nonstop=true ;;
darwin=true
;;
MSYS* | MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
@@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
if [ -n "$JAVA_HOME" ] ; then if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables # IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java" JAVACMD=$JAVA_HOME/jre/sh/java
else else
JAVACMD="$JAVA_HOME/bin/java" JAVACMD=$JAVA_HOME/bin/java
fi fi
if [ ! -x "$JAVACMD" ] ; then if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation." 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. which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the Please set the JAVA_HOME variable in your environment to match the
@@ -106,80 +140,95 @@ location of your Java installation."
fi fi
# Increase the maximum file descriptors if we can. # Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
MAX_FD_LIMIT=`ulimit -H -n` case $MAX_FD in #(
if [ $? -eq 0 ] ; then max*)
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD=$( ulimit -H -n ) ||
MAX_FD="$MAX_FD_LIMIT" warn "Could not query maximum file descriptor limit"
fi esac
ulimit -n $MAX_FD case $MAX_FD in #(
if [ $? -ne 0 ] ; then '' | soft) :;; #(
warn "Could not set maximum file descriptor limit: $MAX_FD" *)
fi ulimit -n "$MAX_FD" ||
else warn "Could not set maximum file descriptor limit to $MAX_FD"
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac esac
fi fi
# Escape application args # Collect all arguments for the java command, stacking in reverse order:
save () { # * args from the command line
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done # * the main class name
echo " " # * -classpath
} # * -D...appname settings
APP_ARGS=`save "$@"` # * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# Collect all arguments for the java command, following the shell quoting and substitution rules # For Cygwin or MSYS, switch paths to Windows format before running java
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@" exec "$JAVACMD" "$@"

View File

@@ -3,16 +3,24 @@ package app.revanced.patcher
import app.revanced.patcher.data.PatcherData import app.revanced.patcher.data.PatcherData
import app.revanced.patcher.data.base.Data import app.revanced.patcher.data.base.Data
import app.revanced.patcher.data.implementation.findIndexed import app.revanced.patcher.data.implementation.findIndexed
import app.revanced.patcher.extensions.PatchExtensions.dependencies
import app.revanced.patcher.extensions.PatchExtensions.patchName
import app.revanced.patcher.extensions.nullOutputStream
import app.revanced.patcher.patch.base.Patch import app.revanced.patcher.patch.base.Patch
import app.revanced.patcher.patch.implementation.BytecodePatch import app.revanced.patcher.patch.implementation.BytecodePatch
import app.revanced.patcher.patch.implementation.ResourcePatch import app.revanced.patcher.patch.implementation.ResourcePatch
import app.revanced.patcher.patch.implementation.metadata.PatchMetadata import app.revanced.patcher.patch.implementation.misc.PatchResult
import app.revanced.patcher.patch.implementation.misc.PatchResultError
import app.revanced.patcher.patch.implementation.misc.PatchResultSuccess import app.revanced.patcher.patch.implementation.misc.PatchResultSuccess
import app.revanced.patcher.signature.MethodSignature import app.revanced.patcher.signature.implementation.method.resolver.MethodSignatureResolver
import app.revanced.patcher.signature.resolver.SignatureResolver
import app.revanced.patcher.util.ListBackedSet import app.revanced.patcher.util.ListBackedSet
import brut.androlib.Androlib import brut.androlib.Androlib
import brut.androlib.meta.UsesFramework import brut.androlib.meta.UsesFramework
import brut.androlib.res.AndrolibResources
import brut.androlib.res.data.ResPackage
import brut.androlib.res.decoder.AXmlResourceParser
import brut.androlib.res.decoder.ResAttrDecoder
import brut.androlib.res.decoder.XmlPullStreamDecoder
import brut.directory.ExtFile import brut.directory.ExtFile
import lanchon.multidexlib2.BasicDexFileNamer import lanchon.multidexlib2.BasicDexFileNamer
import lanchon.multidexlib2.DexIO import lanchon.multidexlib2.DexIO
@@ -27,52 +35,64 @@ val NAMER = BasicDexFileNamer()
/** /**
* The ReVanced Patcher. * The ReVanced Patcher.
* @param inputFile The input file (usually an apk file). * @param options The options for the patcher.
* @param resourceCacheDirectory Directory to cache resources.
* @param patchResources Weather to use the resource patcher. Resources will still need to be decoded.
*/ */
class Patcher( class Patcher(
inputFile: File, private val options: PatcherOptions
// TODO: maybe a file system in memory is better. Could cause high memory usage.
private val resourceCacheDirectory: String,
private val patchResources: Boolean = false
) { ) {
val packageVersion: String val packageVersion: String
val packageName: String val packageName: String
private val usesFramework: UsesFramework private lateinit var usesFramework: UsesFramework
private val patcherData: PatcherData private val patcherData: PatcherData
private val opcodes: Opcodes private val opcodes: Opcodes
private var signaturesResolved = false
private val androlib = Androlib()
init { init {
val extFileInput = ExtFile(inputFile) val extFileInput = ExtFile(options.inputFile)
val resourceTable = androlib.getResTable(extFileInput, true) val outDir = File(options.resourceCacheDirectory)
val outDir = File(resourceCacheDirectory)
if (outDir.exists()) outDir.deleteRecursively() if (outDir.exists()) outDir.deleteRecursively()
outDir.mkdir() outDir.mkdir()
// 1. decode resources to cache directory // load the resource table from the input file
androlib.decodeManifestWithResources(extFileInput, outDir, resourceTable) val androlib = Androlib()
androlib.decodeResourcesFull(extFileInput, outDir, resourceTable) val resourceTable = androlib.getResTable(extFileInput, true)
// 2. read framework ids from the resource table if (options.patchResources) {
usesFramework = UsesFramework() // 1. decode resources to cache directory
usesFramework.ids = resourceTable.listFramePackages().map { it.id }.sorted() androlib.decodeManifestWithResources(extFileInput, outDir, resourceTable)
androlib.decodeResourcesFull(extFileInput, outDir, resourceTable)
// 3. read package info // 2. read framework ids from the resource table
packageName = resourceTable.packageOriginal usesFramework = UsesFramework()
usesFramework.ids = resourceTable.listFramePackages().map { it.id }.sorted()
} else {
// create decoder for the resource table
val decoder = ResAttrDecoder()
decoder.currentPackage = ResPackage(resourceTable, 0, null)
// create xml parser with the decoder
val axmlParser = AXmlResourceParser()
axmlParser.attrDecoder = decoder
// 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
XmlPullStreamDecoder(
axmlParser, AndrolibResources().resXmlSerializer
).decodeManifest(
extFileInput.directory.getFileInput("AndroidManifest.xml"), nullOutputStream
)
}
// set package information
packageVersion = resourceTable.versionInfo.versionName packageVersion = resourceTable.versionInfo.versionName
packageName = resourceTable.currentResPackage.name
// read dex files // read dex files
val dexFile = MultiDexIO.readDexFile(true, inputFile, NAMER, null, null) val dexFile = MultiDexIO.readDexFile(true, options.inputFile, NAMER, null, null)
opcodes = dexFile.opcodes opcodes = dexFile.opcodes
// save to patcher data // save to patcher data
patcherData = PatcherData(dexFile.classes.toMutableList(), resourceCacheDirectory) patcherData = PatcherData(dexFile.classes.toMutableList(), options.resourceCacheDirectory)
} }
/** /**
@@ -82,9 +102,7 @@ class Patcher(
* @param throwOnDuplicates If this is set to true, the patcher will throw an exception if a duplicate class has been found. * @param throwOnDuplicates If this is set to true, the patcher will throw an exception if a duplicate class has been found.
*/ */
fun addFiles( fun addFiles(
files: Iterable<File>, files: Iterable<File>, allowedOverwrites: Iterable<String> = emptyList(), throwOnDuplicates: Boolean = false
allowedOverwrites: Iterable<String> = emptyList(),
throwOnDuplicates: Boolean = false
) { ) {
for (file in files) { for (file in files) {
val dexFile = MultiDexIO.readDexFile(true, file, NAMER, null, null) val dexFile = MultiDexIO.readDexFile(true, file, NAMER, null, null)
@@ -121,47 +139,77 @@ class Patcher(
} }
// build modified resources // build modified resources
if (patchResources) { if (options.patchResources) {
val extDir = ExtFile(resourceCacheDirectory) val extDir = ExtFile(options.resourceCacheDirectory)
androlib.buildResources(extDir, usesFramework)
// TODO: figure out why a new instance of Androlib is necessary here
Androlib().buildResources(extDir, usesFramework)
} }
// write dex modified files // write dex modified files
val output = mutableMapOf<String, MemoryDataStore>() val output = mutableMapOf<String, MemoryDataStore>()
MultiDexIO.writeDexFile( MultiDexIO.writeDexFile(
true, -1, // core count true, -1, // core count
output, NAMER, newDexFile, output, NAMER, newDexFile, DexIO.DEFAULT_MAX_DEX_POOL_SIZE, null
DexIO.DEFAULT_MAX_DEX_POOL_SIZE,
null
) )
return output return output
} }
/** /**
* Add a patch to the patcher. * Add [Patch]es to the patcher.
* @param patches The patches to add. * @param patches [Patch]es The patches to add.
*/ */
fun addPatches(patches: Iterable<Patch<Data>>) { fun addPatches(patches: Iterable<Class<out Patch<Data>>>) {
patcherData.patches.addAll(patches) patcherData.patches.addAll(patches)
} }
/** /**
* Resolves all signatures. * Apply a [patch] and its dependencies recursively.
* @param patch The [patch] to apply.
* @param appliedPatches A list of [patch] names, to prevent applying [patch]es twice.
* @return The result of executing the [patch].
*/ */
fun resolveSignatures(): List<MethodSignature> { private fun applyPatch(
val signatures = buildList { patch: Class<out Patch<Data>>, appliedPatches: MutableList<String>
for (patch in patcherData.patches) { ): PatchResult {
if (patch !is BytecodePatch) continue val patchName = patch.patchName
this.addAll(patch.signatures)
} // if the patch has already applied silently skip it
} if (appliedPatches.contains(patchName)) return PatchResultSuccess()
if (signatures.isEmpty()) { appliedPatches.add(patchName)
return emptyList()
// recursively apply all dependency patches
patch.dependencies?.forEach {
val patchDependency = it.java
val result = applyPatch(patchDependency, appliedPatches)
if (result.isSuccess()) return@forEach
val errorMessage = result.error()!!.message
return PatchResultError("$patchName depends on ${patchDependency.patchName} but the following error was raised: $errorMessage")
} }
SignatureResolver(patcherData.bytecodeData.classes.internalClasses, signatures).resolve(patcherData) val patchInstance = patch.getDeclaredConstructor().newInstance()
signaturesResolved = true
return signatures // if the current patch is a resource patch but resource patching is disabled, return an error
val isResourcePatch = patchInstance is ResourcePatch
if (!options.patchResources && isResourcePatch) return PatchResultError("$patchName is a resource patch, but resource patching is disabled.")
// TODO: find a solution for this
val data = if (isResourcePatch) {
patcherData.resourceData
} else {
MethodSignatureResolver(
patcherData.bytecodeData.classes.internalClasses, (patchInstance as BytecodePatch).signatures
).resolve(patcherData)
patcherData.bytecodeData
}
return try {
patchInstance.execute(data)
} catch (e: Exception) {
PatchResultError(e)
}
} }
/** /**
@@ -172,37 +220,24 @@ class Patcher(
* If the [Patch] failed to apply, an Exception will always be returned to the wrapping Result object. * If the [Patch] failed to apply, an Exception will always be returned to the wrapping Result object.
*/ */
fun applyPatches( fun applyPatches(
stopOnError: Boolean = false, stopOnError: Boolean = false, callback: (String) -> Unit = {}
callback: (String) -> Unit = {} ): Map<String, Result<PatchResultSuccess>> {
): Map<PatchMetadata, Result<PatchResultSuccess>> { val appliedPatches = mutableListOf<String>()
if (!signaturesResolved) {
resolveSignatures()
}
return buildMap { return buildMap {
for (patch in patcherData.patches) { for (patch in patcherData.patches) {
val resourcePatch = patch is ResourcePatch val result = applyPatch(patch, appliedPatches)
if (!patchResources && resourcePatch) continue
callback(patch.metadata.shortName) val name = patch.patchName
val result: Result<PatchResultSuccess> = try { callback(name)
val data = if (resourcePatch) {
patcherData.resourceData
} else {
patcherData.bytecodeData
}
val pr = patch.execute(data) this[name] = if (result.isSuccess()) {
Result.success(result.success()!!)
if (pr.isSuccess()) { } else {
Result.success(pr.success()!!) Result.failure(result.error()!!)
} else {
Result.failure(Exception(pr.error()?.errorMessage() ?: "Unknown error"))
}
} catch (e: Exception) {
Result.failure(e)
} }
this[patch.metadata] = result
if (result.isFailure && stopOnError) break if (stopOnError && result.isError()) break
} }
} }
} }

View File

@@ -0,0 +1,15 @@
package app.revanced.patcher
import java.io.File
/**
* @param inputFile The input file (usually an apk file).
* @param resourceCacheDirectory Directory to cache resources.
* @param patchResources Weather to use the resource patcher. Resources will still need to be decoded.
*/
data class PatcherOptions(
internal val inputFile: File,
// TODO: maybe a file system in memory is better. Could cause high memory usage.
internal val resourceCacheDirectory: String,
internal val patchResources: Boolean = false
)

View File

@@ -0,0 +1,28 @@
package app.revanced.patcher.annotation
import app.revanced.patcher.patch.base.Patch
import app.revanced.patcher.signature.implementation.method.MethodSignature
/**
* Annotation to constrain a [Patch] or [MethodSignature] to compatible packages.
* @param compatiblePackages A list of packages a [Patch] or [MethodSignature] is compatible with.
*/
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
annotation class Compatibility(
val compatiblePackages: Array<Package>,
)
/**
* Annotation to represent packages a patch can be compatible with.
* @param name The package identifier name.
* @param versions The versions of the package the [Patch] or [MethodSignature]is compatible with.
*/
@Target()
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
annotation class Package(
val name: String,
val versions: Array<String>
)

View File

@@ -0,0 +1,38 @@
package app.revanced.patcher.annotation
import app.revanced.patcher.patch.base.Patch
import app.revanced.patcher.signature.implementation.method.MethodSignature
/**
* Annotation to name a [Patch] or [MethodSignature].
* @param name A suggestive name for the [Patch] or [MethodSignature].
*/
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
annotation class Name(
val name: String,
)
/**
* Annotation to describe a [Patch] or [MethodSignature].
* @param description A description for the [Patch] or [MethodSignature].
*/
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
annotation class Description(
val description: String,
)
/**
* Annotation to version a [Patch] or [MethodSignature].
* @param version The version of a [Patch] or [MethodSignature].
*/
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
annotation class Version(
val version: String,
)

View File

@@ -11,8 +11,8 @@ internal data class PatcherData(
val internalClasses: MutableList<ClassDef>, val internalClasses: MutableList<ClassDef>,
val resourceCacheDirectory: String val resourceCacheDirectory: String
) { ) {
internal val patches = mutableListOf<Patch<Data>>() internal val patches = mutableListOf<Class<out Patch<Data>>>()
internal val bytecodeData = BytecodeData(patches, internalClasses) internal val bytecodeData = BytecodeData(internalClasses)
internal val resourceData = ResourceData(File(resourceCacheDirectory)) internal val resourceData = ResourceData(File(resourceCacheDirectory))
} }

View File

@@ -1,56 +1,33 @@
package app.revanced.patcher.data.implementation package app.revanced.patcher.data.implementation
import app.revanced.patcher.data.base.Data import app.revanced.patcher.data.base.Data
import app.revanced.patcher.methodWalker.MethodWalker
import app.revanced.patcher.patch.base.Patch
import app.revanced.patcher.patch.implementation.BytecodePatch
import app.revanced.patcher.proxy.ClassProxy
import app.revanced.patcher.signature.SignatureResolverResult
import app.revanced.patcher.util.ProxyBackedClassList import app.revanced.patcher.util.ProxyBackedClassList
import app.revanced.patcher.util.method.MethodWalker
import org.jf.dexlib2.iface.ClassDef import org.jf.dexlib2.iface.ClassDef
import org.jf.dexlib2.iface.Method import org.jf.dexlib2.iface.Method
class BytecodeData( class BytecodeData(
// FIXME: ugly solution due to design.
// It does not make sense for a BytecodeData instance to have access to the patches
private val patches: List<Patch<Data>>,
internalClasses: MutableList<ClassDef> internalClasses: MutableList<ClassDef>
) : Data { ) : Data {
val classes = ProxyBackedClassList(internalClasses) val classes = ProxyBackedClassList(internalClasses)
/** /**
* Find a class by a given class name * Find a class by a given class name.
* @return A proxy for the first class that matches the class name * @param className The name of the class.
* @return A proxy for the first class that matches the class name.
*/ */
fun findClass(className: String) = findClass { it.type.contains(className) } fun findClass(className: String) = findClass { it.type.contains(className) }
/** /**
* Find a class by a given predicate * Find a class by a given predicate.
* @return A proxy for the first class that matches the predicate * @param predicate A predicate to match the class.
* @return A proxy for the first class that matches the predicate.
*/ */
fun findClass(predicate: (ClassDef) -> Boolean): ClassProxy? { fun findClass(predicate: (ClassDef) -> Boolean) =
// if we already proxied the class matching the predicate... // if we already proxied the class matching the predicate...
for (patch in patches) { classes.proxies.firstOrNull { predicate(it.immutableClass) } ?:
if (patch !is BytecodePatch) continue
for (signature in patch.signatures) {
val result = signature.result
result ?: continue
if (predicate(result.definingClassProxy.immutableClass)) return result.definingClassProxy // ...then return that proxy
}
}
// else resolve the class to a proxy and return it, if the predicate is matching a class // else resolve the class to a proxy and return it, if the predicate is matching a class
return classes.find(predicate)?.let { classes.find(predicate)?.let { proxy(it) }
proxy(it)
}
}
}
class MethodMap : LinkedHashMap<String, SignatureResolverResult>() {
override fun get(key: String): SignatureResolverResult {
return super.get(key) ?: throw MethodNotFoundException("Method $key was not found in the method cache")
}
} }
internal class MethodNotFoundException(s: String) : Exception(s) internal class MethodNotFoundException(s: String) : Exception(s)
@@ -64,6 +41,11 @@ internal inline fun <reified T> Iterable<T>.find(predicate: (T) -> Boolean): T?
return null return null
} }
/**
* Create a [MethodWalker] instance for the current [BytecodeData].
* @param startMethod The method to start at.
* @return A [MethodWalker] instance.
*/
fun BytecodeData.toMethodWalker(startMethod: Method): MethodWalker { fun BytecodeData.toMethodWalker(startMethod: Method): MethodWalker {
return MethodWalker(this, startMethod) return MethodWalker(this, startMethod)
} }
@@ -77,11 +59,11 @@ internal inline fun <T> Iterable<T>.findIndexed(predicate: (T) -> Boolean): Pair
return null return null
} }
fun BytecodeData.proxy(classDef: ClassDef): ClassProxy { fun BytecodeData.proxy(classDef: ClassDef): app.revanced.patcher.util.proxy.ClassProxy {
var proxy = this.classes.proxies.find { it.immutableClass.type == classDef.type } var proxy = this.classes.proxies.find { it.immutableClass.type == classDef.type }
if (proxy == null) { if (proxy == null) {
proxy = ClassProxy(classDef) proxy = app.revanced.patcher.util.proxy.ClassProxy(classDef)
this.classes.proxies.add(proxy) this.classes.add(proxy)
} }
return proxy return proxy
} }

View File

@@ -14,8 +14,7 @@ class ResourceData(private val resourceCacheDirectory: File) : Data {
private fun resolve(path: String) = resourceCacheDirectory.resolve(path) private fun resolve(path: String) = resourceCacheDirectory.resolve(path)
fun forEach(action: (File) -> Unit) = resourceCacheDirectory.walkTopDown().forEach(action) fun forEach(action: (File) -> Unit) = resourceCacheDirectory.walkTopDown().forEach(action)
fun reader(path: String) = resolve(path).reader() fun get(path: String) = resolve(path)
fun writer(path: String) = resolve(path).writer()
fun replace(path: String, oldValue: String, newValue: String, oldValueIsRegex: Boolean = false) { fun replace(path: String, oldValue: String, newValue: String, oldValueIsRegex: Boolean = false) {
// TODO: buffer this somehow // TODO: buffer this somehow

View File

@@ -0,0 +1,57 @@
package app.revanced.patcher.extensions
import app.revanced.patcher.annotation.Compatibility
import app.revanced.patcher.annotation.Description
import app.revanced.patcher.annotation.Name
import app.revanced.patcher.annotation.Version
import app.revanced.patcher.data.base.Data
import app.revanced.patcher.patch.base.Patch
import app.revanced.patcher.signature.implementation.method.MethodSignature
import app.revanced.patcher.signature.implementation.method.annotation.FuzzyPatternScanMethod
import app.revanced.patcher.signature.implementation.method.annotation.MatchingMethod
import kotlin.reflect.KClass
/**
* Recursively find a given annotation on a class.
* @param targetAnnotation The annotation to find.
* @return The annotation.
*/
private fun <T : Annotation> Class<*>.recursiveAnnotation(targetAnnotation: KClass<T>) =
this.findAnnotationRecursively(targetAnnotation.java, mutableSetOf())
private fun <T : Annotation> Class<*>.findAnnotationRecursively(
targetAnnotation: Class<T>, traversed: MutableSet<Annotation>
): T? {
val found = this.annotations.firstOrNull { it.annotationClass.java.name == targetAnnotation.name }
@Suppress("UNCHECKED_CAST") if (found != null) return found as T
for (annotation in this.annotations) {
if (traversed.contains(annotation)) continue
traversed.add(annotation)
return (annotation.annotationClass.java.findAnnotationRecursively(targetAnnotation, traversed)) ?: continue
}
return null
}
object PatchExtensions {
val Class<out Patch<Data>>.patchName: String
get() = recursiveAnnotation(Name::class)?.name ?: this.javaClass.simpleName
val Class<out Patch<Data>>.version get() = recursiveAnnotation(Version::class)?.version
val Class<out Patch<Data>>.description get() = recursiveAnnotation(Description::class)?.description
val Class<out Patch<Data>>.dependencies get() = recursiveAnnotation(app.revanced.patcher.patch.annotations.Dependencies::class)?.dependencies
val Class<out Patch<Data>>.compatiblePackages get() = recursiveAnnotation(Compatibility::class)?.compatiblePackages
}
object MethodSignatureExtensions {
val MethodSignature.name: String get() = javaClass.recursiveAnnotation(Name::class)?.name ?: this.javaClass.simpleName
val MethodSignature.version get() = javaClass.recursiveAnnotation(Version::class)?.version ?: "0.0.1"
val MethodSignature.description get() = javaClass.recursiveAnnotation(Description::class)?.description
val MethodSignature.compatiblePackages get() = javaClass.recursiveAnnotation(Compatibility::class)?.compatiblePackages
val MethodSignature.matchingMethod get() = javaClass.recursiveAnnotation(MatchingMethod::class)
val MethodSignature.fuzzyPatternScanMethod get() = javaClass.recursiveAnnotation(FuzzyPatternScanMethod::class)
val MethodSignature.fuzzyThreshold get() = fuzzyPatternScanMethod?.threshold ?: 0
}

View File

@@ -1,6 +1,6 @@
package app.revanced.patcher.extensions package app.revanced.patcher.extensions
import app.revanced.patcher.proxy.mutableTypes.MutableMethod.Companion.toMutable import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
import org.jf.dexlib2.AccessFlags import org.jf.dexlib2.AccessFlags
import org.jf.dexlib2.builder.BuilderInstruction import org.jf.dexlib2.builder.BuilderInstruction
import org.jf.dexlib2.builder.MutableMethodImplementation import org.jf.dexlib2.builder.MutableMethodImplementation
@@ -9,6 +9,7 @@ import org.jf.dexlib2.iface.reference.MethodReference
import org.jf.dexlib2.immutable.ImmutableMethod import org.jf.dexlib2.immutable.ImmutableMethod
import org.jf.dexlib2.immutable.ImmutableMethodImplementation import org.jf.dexlib2.immutable.ImmutableMethodImplementation
import org.jf.dexlib2.util.MethodUtil import org.jf.dexlib2.util.MethodUtil
import java.io.OutputStream
infix fun AccessFlags.or(other: AccessFlags) = this.value or other.value infix fun AccessFlags.or(other: AccessFlags) = this.value or other.value
infix fun Int.or(other: AccessFlags) = this or other.value infix fun Int.or(other: AccessFlags) = this or other.value
@@ -19,6 +20,19 @@ fun MutableMethodImplementation.addInstructions(index: Int, instructions: List<B
} }
} }
/**
* Compare a method to another, considering constructors and parameters.
* @param otherMethod The method to compare against.
* @return True if the methods match given the conditions.
*/
fun Method.softCompareTo(
otherMethod: MethodReference
): Boolean {
if (MethodUtil.isConstructor(this) && !parametersEqual(this.parameterTypes, otherMethod.parameterTypes))
return false
return this.name == otherMethod.name
}
/** /**
* Clones the method. * Clones the method.
* @param registerCount This parameter allows you to change the register count of the method. * @param registerCount This parameter allows you to change the register count of the method.
@@ -58,14 +72,6 @@ internal fun Method.cloneMutable(
registerCount: Int = 0, registerCount: Int = 0,
) = clone(registerCount).toMutable() ) = clone(registerCount).toMutable()
internal fun Method.softCompareTo(
otherMethod: MethodReference
): Boolean {
if (MethodUtil.isConstructor(this) && !parametersEqual(this.parameterTypes, otherMethod.parameterTypes))
return false
return this.name == otherMethod.name
}
// FIXME: also check the order of parameters as different order equals different method overload // FIXME: also check the order of parameters as different order equals different method overload
internal fun parametersEqual( internal fun parametersEqual(
parameters1: Iterable<CharSequence>, parameters1: Iterable<CharSequence>,
@@ -78,4 +84,9 @@ internal fun parametersEqual(
) )
} }
} }
} }
internal val nullOutputStream: OutputStream =
object : OutputStream() {
override fun write(b: Int) {}
}

View File

@@ -0,0 +1,23 @@
package app.revanced.patcher.patch.annotations
import app.revanced.patcher.data.base.Data
import app.revanced.patcher.patch.base.Patch
import kotlin.reflect.KClass
/**
* Annotation to mark a Class as a patch.
*/
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
annotation class Patch
/**
* Annotation for dependencies of [Patch]es .
*/
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
annotation class Dependencies(
val dependencies: Array<KClass<out Patch<Data>>> = []
)

View File

@@ -3,20 +3,17 @@ package app.revanced.patcher.patch.base
import app.revanced.patcher.data.base.Data import app.revanced.patcher.data.base.Data
import app.revanced.patcher.patch.implementation.BytecodePatch import app.revanced.patcher.patch.implementation.BytecodePatch
import app.revanced.patcher.patch.implementation.ResourcePatch import app.revanced.patcher.patch.implementation.ResourcePatch
import app.revanced.patcher.patch.implementation.metadata.PatchMetadata
import app.revanced.patcher.patch.implementation.misc.PatchResult import app.revanced.patcher.patch.implementation.misc.PatchResult
/** /**
* A ReVanced patch. * A ReVanced patch.
* Can either be a [ResourcePatch] or a [BytecodePatch] * Can either be a [ResourcePatch] or a [BytecodePatch].
*/ */
abstract class Patch<out T : Data>( abstract class Patch<out T : Data> {
open val metadata: PatchMetadata
) {
/** /**
* The main function of the [Patch] which the patcher will call. * The main function of the [Patch] which the patcher will call.
*/ */
abstract fun execute(data: @UnsafeVariance T): PatchResult // FIXME: remove the UnsafeVariance annotation abstract fun execute(data: @UnsafeVariance T): PatchResult
} }

View File

@@ -2,15 +2,12 @@ package app.revanced.patcher.patch.implementation
import app.revanced.patcher.data.implementation.BytecodeData import app.revanced.patcher.data.implementation.BytecodeData
import app.revanced.patcher.patch.base.Patch import app.revanced.patcher.patch.base.Patch
import app.revanced.patcher.patch.implementation.metadata.PatchMetadata import app.revanced.patcher.signature.implementation.method.MethodSignature
import app.revanced.patcher.signature.MethodSignature
/** /**
* Bytecode patch for the Patcher. * Bytecode patch for the Patcher.
* @param metadata [PatchMetadata] for the patch.
* @param signatures A list of [MethodSignature] this patch relies on. * @param signatures A list of [MethodSignature] this patch relies on.
*/ */
abstract class BytecodePatch( abstract class BytecodePatch(
override val metadata: PatchMetadata,
val signatures: Iterable<MethodSignature> val signatures: Iterable<MethodSignature>
) : Patch<BytecodeData>(metadata) ) : Patch<BytecodeData>()

View File

@@ -2,12 +2,8 @@ package app.revanced.patcher.patch.implementation
import app.revanced.patcher.data.implementation.ResourceData import app.revanced.patcher.data.implementation.ResourceData
import app.revanced.patcher.patch.base.Patch import app.revanced.patcher.patch.base.Patch
import app.revanced.patcher.patch.implementation.metadata.PatchMetadata
/** /**
* Resource patch for the Patcher. * Resource patch for the Patcher.
* @param metadata [PatchMetadata] for the patch.
*/ */
abstract class ResourcePatch( abstract class ResourcePatch : Patch<ResourceData>()
override val metadata: PatchMetadata
) : Patch<ResourceData>(metadata)

View File

@@ -1,29 +0,0 @@
package app.revanced.patcher.patch.implementation.metadata
import app.revanced.patcher.patch.base.Patch
/**
* Metadata about a [Patch].
* @param shortName A suggestive short name for the [Patch].
* @param name A suggestive name for the [Patch].
* @param description A description for the [Patch].
* @param compatiblePackages A list of packages this [Patch] is compatible with.
* @param version The version of the [Patch].
*/
data class PatchMetadata(
val shortName: String,
val name: String,
val description: String,
val compatiblePackages: Iterable<PackageMetadata>,
val version: String,
)
/**
* Metadata about a package.
* @param name The package name.
* @param versions Compatible versions of the package.
*/
data class PackageMetadata(
val name: String,
val versions: Iterable<String>
)

View File

@@ -24,10 +24,12 @@ interface PatchResult {
} }
} }
class PatchResultError(private val errorMessage: String) : PatchResult { class PatchResultError(
fun errorMessage(): String { errorMessage: String?, cause: Exception?
return errorMessage ) : Exception(errorMessage, cause), PatchResult {
} constructor(errorMessage: String) : this(errorMessage, null)
constructor(cause: Exception) : this(cause.message, cause)
} }
class PatchResultSuccess : PatchResult class PatchResultSuccess : PatchResult

View File

@@ -1,111 +0,0 @@
package app.revanced.patcher.signature
import app.revanced.patcher.data.implementation.MethodNotFoundException
import app.revanced.patcher.patch.implementation.metadata.PackageMetadata
import org.jf.dexlib2.Opcode
/**
* Represents the [MethodSignature] for a method.
* @param metadata Metadata for this [MethodSignature].
* @param returnType The return type of the method.
* @param accessFlags The access flags of the method.
* @param methodParameters The parameters of the method.
* @param opcodes The list of opcodes of the method.
* @param strings A list of strings which a method contains.
* A `null` opcode is equals to an unknown opcode.
*/
class MethodSignature(
val metadata: MethodSignatureMetadata,
internal val returnType: String?,
internal val accessFlags: Int?,
internal val methodParameters: Iterable<String>?,
internal val opcodes: Iterable<Opcode?>?,
internal val strings: Iterable<String>? = null
) {
/**
* The result of the signature
*/
var result: SignatureResolverResult? = null
get() {
return field ?: throw MethodNotFoundException(
"Could not resolve required signature ${metadata.name}"
)
}
val resolved: Boolean
get() {
var resolved = false
try {
resolved = result != null
} catch (_: Exception) {
}
return resolved
}
}
/**
* Metadata about a [MethodSignature].
* @param name A suggestive name for the [MethodSignature].
* @param methodMetadata Metadata about the method for the [MethodSignature].
* @param patternScanMethod The pattern scanning method the pattern scanner should rely on.
* Can either be [PatternScanMethod.Fuzzy] or [PatternScanMethod.Direct].
* @param description An optional description for the [MethodSignature].
* @param compatiblePackages The list of packages the [MethodSignature] is compatible with.
* @param version The version of this signature.
*/
data class MethodSignatureMetadata(
val name: String,
val methodMetadata: MethodMetadata?,
val patternScanMethod: PatternScanMethod,
val compatiblePackages: Iterable<PackageMetadata>,
val description: String?,
val version: String
)
/**
* Metadata about the method for a [MethodSignature].
* @param definingClass The defining class name of the method.
* @param name A suggestive name for the method which the [MethodSignature] was created for.
*/
data class MethodMetadata(
val definingClass: String?,
val name: String?
)
/**
* The method, the patcher should rely on when scanning the opcode pattern of a [MethodSignature]
*/
interface PatternScanMethod {
/**
* When comparing the signature, if one or more of the opcodes do not match, skip.
*/
class Direct : PatternScanMethod
/**
* When comparing the signature, if [threshold] or more of the opcodes do not match, skip.
*/
class Fuzzy(internal val threshold: Int) : PatternScanMethod {
/**
* A list of warnings the resolver found.
*
* This list will be allocated when the signature has been found.
* Meaning, if the signature was not found,
* or the signature was not yet resolved,
* the list will be null.
*/
var warnings: List<Warning>? = null
/**
* Represents a resolver warning.
* @param correctOpcode The opcode the instruction list has.
* @param wrongOpcode The opcode the pattern list of the signature currently has.
* @param instructionIndex The index of the opcode relative to the instruction list.
* @param patternIndex The index of the opcode relative to the pattern list from the signature.
*/
data class Warning(
val correctOpcode: Opcode,
val wrongOpcode: Opcode,
val instructionIndex: Int,
val patternIndex: Int,
)
}
}

View File

@@ -1,48 +0,0 @@
package app.revanced.patcher.signature
import app.revanced.patcher.extensions.softCompareTo
import app.revanced.patcher.proxy.ClassProxy
import app.revanced.patcher.signature.resolver.SignatureResolver
import org.jf.dexlib2.iface.Method
/**
* Represents the result of a [SignatureResolver].
* @param definingClassProxy The [ClassProxy] that the matching method was found in.
* @param resolvedMethod The actual matching method.
* @param scanData Opcodes pattern scan result.
*/
data class SignatureResolverResult(
val definingClassProxy: ClassProxy,
val scanData: PatternScanResult,
private val resolvedMethod: Method,
) {
/**
* Returns the **mutable** method by the [resolvedMethod] from the [definingClassProxy].
*
* Please note, this method allocates a [ClassProxy].
* Use [immutableMethod] where possible.
*/
val method
get() = definingClassProxy.resolve().methods.first {
it.softCompareTo(resolvedMethod)
}
/**
* Returns the **immutable** method by the [resolvedMethod] from the [definingClassProxy].
*
* If you need to modify the method, use [method] instead.
*/
val immutableMethod: Method
get() = definingClassProxy.immutableClass.methods.first {
it.softCompareTo(resolvedMethod)
}
fun findParentMethod(signature: MethodSignature): SignatureResolverResult? {
return SignatureResolver.resolveFromProxy(definingClassProxy, signature)
}
}
data class PatternScanResult(
val startIndex: Int,
val endIndex: Int
)

View File

@@ -0,0 +1,9 @@
package app.revanced.patcher.signature.base
import app.revanced.patcher.signature.implementation.method.MethodSignature
/**
* A ReVanced signature.
* Can be a [MethodSignature].
*/
interface Signature

View File

@@ -0,0 +1,33 @@
package app.revanced.patcher.signature.implementation.method
import app.revanced.patcher.data.implementation.MethodNotFoundException
import app.revanced.patcher.extensions.MethodSignatureExtensions.name
import app.revanced.patcher.signature.base.Signature
import app.revanced.patcher.signature.implementation.method.resolver.SignatureResolverResult
import org.jf.dexlib2.Opcode
/**
* Represents the [MethodSignature] for a method.
* @param returnType The return type of the method.
* @param accessFlags The access flags of the method.
* @param methodParameters The parameters of the method.
* @param opcodes The list of opcodes of the method.
* @param strings A list of strings which a method contains.
* A `null` opcode is equals to an unknown opcode.
*/
abstract class MethodSignature(
internal val returnType: String?,
internal val accessFlags: Int?,
internal val methodParameters: Iterable<String>?,
internal val opcodes: Iterable<Opcode?>?,
internal val strings: Iterable<String>? = null
) : Signature {
/**
* The result of the signature
*/
var result: SignatureResolverResult? = null
@Throws(MethodNotFoundException::class)
get() {
return field ?: throw MethodNotFoundException("Could not resolve required signature ${this.name}")
}
}

View File

@@ -0,0 +1,32 @@
package app.revanced.patcher.signature.implementation.method.annotation
import app.revanced.patcher.signature.implementation.method.MethodSignature
/**
* Annotations for a method which matches to a [MethodSignature].
* @param definingClass The defining class name of the method.
* @param name A suggestive name for the method which the [MethodSignature] was created for.
*/
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class MatchingMethod(
val definingClass: String = "L<unspecified-class>;",
val name: String = "<unspecified-method>"
)
/**
* Annotations to scan a pattern [MethodSignature] with fuzzy algorithm.
* @param threshold if [threshold] or more of the opcodes do not match, skip.
*/
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class FuzzyPatternScanMethod(
val threshold: Int = 1
)
/**
* Annotations to scan a pattern [MethodSignature] directly.
*/
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class DirectPatternScanMethod

View File

@@ -1,13 +1,10 @@
package app.revanced.patcher.signature.resolver package app.revanced.patcher.signature.implementation.method.resolver
import app.revanced.patcher.data.PatcherData import app.revanced.patcher.data.PatcherData
import app.revanced.patcher.data.implementation.proxy import app.revanced.patcher.data.implementation.proxy
import app.revanced.patcher.extensions.MethodSignatureExtensions.fuzzyThreshold
import app.revanced.patcher.extensions.parametersEqual import app.revanced.patcher.extensions.parametersEqual
import app.revanced.patcher.proxy.ClassProxy import app.revanced.patcher.signature.implementation.method.MethodSignature
import app.revanced.patcher.signature.MethodSignature
import app.revanced.patcher.signature.PatternScanMethod
import app.revanced.patcher.signature.PatternScanResult
import app.revanced.patcher.signature.SignatureResolverResult
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
@@ -15,7 +12,7 @@ import org.jf.dexlib2.iface.instruction.Instruction
import org.jf.dexlib2.iface.instruction.formats.Instruction21c import org.jf.dexlib2.iface.instruction.formats.Instruction21c
import org.jf.dexlib2.iface.reference.StringReference import org.jf.dexlib2.iface.reference.StringReference
internal class SignatureResolver( internal class MethodSignatureResolver(
private val classes: List<ClassDef>, private val classes: List<ClassDef>,
private val methodSignatures: Iterable<MethodSignature> private val methodSignatures: Iterable<MethodSignature>
) { ) {
@@ -39,7 +36,10 @@ internal class SignatureResolver(
// These functions do not require the constructor values, so they can be static. // These functions do not require the constructor values, so they can be static.
companion object { companion object {
fun resolveFromProxy(classProxy: ClassProxy, signature: MethodSignature): SignatureResolverResult? { fun resolveFromProxy(
classProxy: app.revanced.patcher.util.proxy.ClassProxy,
signature: MethodSignature
): SignatureResolverResult? {
for (method in classProxy.immutableClass.methods) { for (method in classProxy.immutableClass.methods) {
val result = compareSignatureToMethod(signature, method) ?: continue val result = compareSignatureToMethod(signature, method) ?: continue
return SignatureResolverResult( return SignatureResolverResult(
@@ -107,9 +107,8 @@ internal class SignatureResolver(
val count = instructions.count() val count = instructions.count()
val pattern = signature.opcodes!! val pattern = signature.opcodes!!
val size = pattern.count() val size = pattern.count()
val method = signature.metadata.patternScanMethod
val threshold = if (method is PatternScanMethod.Fuzzy) val threshold = signature.fuzzyThreshold
method.threshold else 0
for (instructionIndex in 0 until count) { for (instructionIndex in 0 until count) {
var patternIndex = 0 var patternIndex = 0
@@ -126,11 +125,9 @@ internal class SignatureResolver(
patternIndex-- // fix pattern offset patternIndex-- // fix pattern offset
val result = PatternScanResult(instructionIndex, instructionIndex + patternIndex) val result = PatternScanResult(instructionIndex, instructionIndex + patternIndex)
if (method is PatternScanMethod.Fuzzy) {
method.warnings = generateWarnings( result.warnings = generateWarnings(signature, instructions, result)
signature, instructions, result
)
}
return result return result
} }
} }
@@ -152,7 +149,7 @@ internal class SignatureResolver(
correctOpcode != patternOpcode correctOpcode != patternOpcode
) { ) {
this.add( this.add(
PatternScanMethod.Fuzzy.Warning( PatternScanResult.Warning(
correctOpcode, patternOpcode, correctOpcode, patternOpcode,
instructionIndex, patternIndex, instructionIndex, patternIndex,
) )

View File

@@ -0,0 +1,75 @@
package app.revanced.patcher.signature.implementation.method.resolver
import app.revanced.patcher.extensions.softCompareTo
import app.revanced.patcher.signature.implementation.method.MethodSignature
import app.revanced.patcher.util.proxy.ClassProxy
import org.jf.dexlib2.Opcode
import org.jf.dexlib2.iface.Method
/**
* Represents the result of a [MethodSignatureResolver].
* @param definingClassProxy The [ClassProxy] that the matching method was found in.
* @param resolvedMethod The actual matching method.
* @param scanResult Opcodes pattern scan result.
*/
data class SignatureResolverResult(
val definingClassProxy: ClassProxy,
val scanResult: PatternScanResult,
private val resolvedMethod: Method,
) {
/**
* Returns the **mutable** method by the [resolvedMethod] from the [definingClassProxy].
*
* Please note, this method allocates a [ClassProxy].
* Use [immutableMethod] where possible.
*/
val method
get() = definingClassProxy.resolve().methods.first {
it.softCompareTo(resolvedMethod)
}
/**
* Returns the **immutable** method by the [resolvedMethod] from the [definingClassProxy].
*
* If you need to modify the method, use [method] instead.
*/
@Suppress("MemberVisibilityCanBePrivate")
val immutableMethod: Method
get() = definingClassProxy.immutableClass.methods.first {
it.softCompareTo(resolvedMethod)
}
fun findParentMethod(signature: MethodSignature): SignatureResolverResult? {
return MethodSignatureResolver.resolveFromProxy(definingClassProxy, signature)
}
}
data class PatternScanResult(
val startIndex: Int,
val endIndex: Int
) {
/**
* A list of warnings the resolver found.
*
* This list will be allocated when the signature has been found.
* Meaning, if the signature was not found,
* or the signature was not yet resolved,
* the list will be null.
*/
var warnings: List<Warning>? = null
/**
* Represents a resolver warning.
* @param correctOpcode The opcode the instruction list has.
* @param wrongOpcode The opcode the pattern list of the signature currently has.
* @param instructionIndex The index of the opcode relative to the instruction list.
* @param patternIndex The index of the opcode relative to the pattern list from the signature.
*/
data class Warning(
val correctOpcode: Opcode,
val wrongOpcode: Opcode,
val instructionIndex: Int,
val patternIndex: Int,
)
}

View File

@@ -1,6 +1,6 @@
package app.revanced.patcher.util package app.revanced.patcher.util
class ListBackedSet<E>(private val list: MutableList<E>) : MutableSet<E> { internal class ListBackedSet<E>(private val list: MutableList<E>) : MutableSet<E> {
override val size get() = list.size override val size get() = list.size
override fun add(element: E) = list.add(element) override fun add(element: E) = list.add(element)
override fun addAll(elements: Collection<E>) = list.addAll(elements) override fun addAll(elements: Collection<E>) = list.addAll(elements)

View File

@@ -1,25 +1,21 @@
package app.revanced.patcher.util package app.revanced.patcher.util
import app.revanced.patcher.proxy.ClassProxy import app.revanced.patcher.util.proxy.ClassProxy
import org.jf.dexlib2.iface.ClassDef import org.jf.dexlib2.iface.ClassDef
class ProxyBackedClassList(internal val internalClasses: MutableList<ClassDef>) : List<ClassDef> { class ProxyBackedClassList(internal val internalClasses: MutableList<ClassDef>) : List<ClassDef> {
internal val proxies = mutableListOf<ClassProxy>() private val internalProxies = mutableListOf<ClassProxy>()
internal val proxies: List<ClassProxy> = internalProxies
fun add(classDef: ClassDef) { fun add(classDef: ClassDef) = internalClasses.add(classDef)
internalClasses.add(classDef) fun add(classProxy: ClassProxy) = internalProxies.add(classProxy)
}
fun add(classProxy: ClassProxy) {
proxies.add(classProxy)
}
/** /**
* Apply all resolved classes into [internalClasses] and clean the [proxies] list. * Apply all resolved classes into [internalClasses] and clean the [proxies] list.
*/ */
fun applyProxies() { internal fun applyProxies() {
// FIXME: check if this could cause issues when multiple patches use the same proxy // FIXME: check if this could cause issues when multiple patches use the same proxy
proxies.removeIf { proxy -> internalProxies.removeIf { proxy ->
// if the proxy is unused, keep it in the list // if the proxy is unused, keep it in the list
if (!proxy.proxyUsed) return@removeIf false if (!proxy.proxyUsed) return@removeIf false

View File

@@ -1,9 +1,9 @@
package app.revanced.patcher.methodWalker package app.revanced.patcher.util.method
import app.revanced.patcher.data.implementation.BytecodeData import app.revanced.patcher.data.implementation.BytecodeData
import app.revanced.patcher.data.implementation.MethodNotFoundException import app.revanced.patcher.data.implementation.MethodNotFoundException
import app.revanced.patcher.extensions.softCompareTo import app.revanced.patcher.extensions.softCompareTo
import app.revanced.patcher.proxy.mutableTypes.MutableMethod import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import org.jf.dexlib2.Format import org.jf.dexlib2.Format
import org.jf.dexlib2.iface.Method import org.jf.dexlib2.iface.Method
import org.jf.dexlib2.iface.instruction.formats.Instruction35c import org.jf.dexlib2.iface.instruction.formats.Instruction35c
@@ -33,7 +33,7 @@ class MethodWalker internal constructor(
* @param walkMutable If this is true, the class of the method will be resolved mutably. * @param walkMutable If this is true, the class of the method will be resolved mutably.
* The current method will be mutable. * The current method will be mutable.
*/ */
fun walk(offset: Int, walkMutable: Boolean = false): MethodWalker { fun nextMethod(offset: Int, walkMutable: Boolean = false): MethodWalker {
currentMethod.implementation?.instructions?.let { instructions -> currentMethod.implementation?.instructions?.let { instructions ->
val instruction = instructions.elementAt(offset) val instruction = instructions.elementAt(offset)

View File

@@ -0,0 +1,18 @@
package app.revanced.patcher.util.patch.base
import app.revanced.patcher.data.base.Data
import app.revanced.patcher.patch.base.Patch
import java.io.File
/**
* @param patchBundlePath The path to the patch bundle.
*/
abstract class PatchBundle(patchBundlePath: String) : File(patchBundlePath) {
internal fun loadPatches(classLoader: ClassLoader, classNames: Iterator<String>) = buildList {
classNames.forEach { className ->
val clazz = classLoader.loadClass(className)
if (!clazz.isAnnotationPresent(app.revanced.patcher.patch.annotations.Patch::class.java)) return@forEach
@Suppress("UNCHECKED_CAST") this.add(clazz as Class<out Patch<Data>>)
}
}
}

View File

@@ -0,0 +1,17 @@
package app.revanced.patcher.util.patch.implementation
import app.revanced.patcher.util.patch.base.PatchBundle
import app.revanced.patcher.util.patch.util.StringIterator
import org.jf.dexlib2.DexFileFactory
/**
* A patch bundle of the ReVanced [DexPatchBundle] format.
* @param patchBundlePath The path to a patch bundle of dex format.
* @param dexClassLoader The dex class loader.
*/
class DexPatchBundle(patchBundlePath: String, private val dexClassLoader: ClassLoader) : PatchBundle(patchBundlePath) {
fun loadPatches() = loadPatches(dexClassLoader,
StringIterator(DexFileFactory.loadDexFile(path, null).classes.iterator()) { classDef ->
classDef.type.substring(1, classDef.length - 1).replace('/', '.')
})
}

View File

@@ -0,0 +1,30 @@
package app.revanced.patcher.util.patch.implementation
import app.revanced.patcher.util.patch.base.PatchBundle
import app.revanced.patcher.util.patch.util.StringIterator
import java.net.URLClassLoader
import java.util.jar.JarFile
/**
* A patch bundle of the ReVanced [JarPatchBundle] format.
* @param patchBundlePath The path to the patch bundle.
*/
class JarPatchBundle(patchBundlePath: String) : PatchBundle(patchBundlePath) {
fun loadPatches() = loadPatches(
URLClassLoader(
arrayOf(this.toURI().toURL()),
Thread.currentThread().contextClassLoader // TODO: find out why this is required
),
StringIterator(
JarFile(this)
.entries()
.toList() // TODO: find a cleaner solution than that to filter non class files
.filter {
it.name.endsWith(".class") && !it.name.contains("$")
}
.iterator()
) {
it.realName.replace('/', '.').replace(".class", "")
}
)
}

View File

@@ -0,0 +1,10 @@
package app.revanced.patcher.util.patch.util
internal class StringIterator<T, I : Iterator<T>>(
private val iterator: I,
private val _next: (T) -> String
) : Iterator<String> {
override fun hasNext() = iterator.hasNext()
override fun next() = _next(iterator.next())
}

View File

@@ -1,6 +1,6 @@
package app.revanced.patcher.proxy package app.revanced.patcher.util.proxy
import app.revanced.patcher.proxy.mutableTypes.MutableClass import app.revanced.patcher.util.proxy.mutableTypes.MutableClass
import org.jf.dexlib2.iface.ClassDef import org.jf.dexlib2.iface.ClassDef
/** /**

View File

@@ -1,6 +1,6 @@
package app.revanced.patcher.proxy.mutableTypes package app.revanced.patcher.util.proxy.mutableTypes
import app.revanced.patcher.proxy.mutableTypes.MutableAnnotationElement.Companion.toMutable import app.revanced.patcher.util.proxy.mutableTypes.MutableAnnotationElement.Companion.toMutable
import org.jf.dexlib2.base.BaseAnnotation import org.jf.dexlib2.base.BaseAnnotation
import org.jf.dexlib2.iface.Annotation import org.jf.dexlib2.iface.Annotation

View File

@@ -1,7 +1,7 @@
package app.revanced.patcher.proxy.mutableTypes package app.revanced.patcher.util.proxy.mutableTypes
import app.revanced.patcher.proxy.mutableTypes.encodedValue.MutableEncodedValue import app.revanced.patcher.util.proxy.mutableTypes.encodedValue.MutableEncodedValue
import app.revanced.patcher.proxy.mutableTypes.encodedValue.MutableEncodedValue.Companion.toMutable import app.revanced.patcher.util.proxy.mutableTypes.encodedValue.MutableEncodedValue.Companion.toMutable
import org.jf.dexlib2.base.BaseAnnotationElement import org.jf.dexlib2.base.BaseAnnotationElement
import org.jf.dexlib2.iface.AnnotationElement import org.jf.dexlib2.iface.AnnotationElement
import org.jf.dexlib2.iface.value.EncodedValue import org.jf.dexlib2.iface.value.EncodedValue

View File

@@ -1,8 +1,8 @@
package app.revanced.patcher.proxy.mutableTypes package app.revanced.patcher.util.proxy.mutableTypes
import app.revanced.patcher.proxy.mutableTypes.MutableAnnotation.Companion.toMutable import app.revanced.patcher.util.proxy.mutableTypes.MutableAnnotation.Companion.toMutable
import app.revanced.patcher.proxy.mutableTypes.MutableField.Companion.toMutable import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable
import app.revanced.patcher.proxy.mutableTypes.MutableMethod.Companion.toMutable import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
import com.google.common.collect.Iterables import com.google.common.collect.Iterables
import org.jf.dexlib2.base.reference.BaseTypeReference import org.jf.dexlib2.base.reference.BaseTypeReference
import org.jf.dexlib2.iface.ClassDef import org.jf.dexlib2.iface.ClassDef

View File

@@ -1,8 +1,8 @@
package app.revanced.patcher.proxy.mutableTypes package app.revanced.patcher.util.proxy.mutableTypes
import app.revanced.patcher.proxy.mutableTypes.MutableAnnotation.Companion.toMutable import app.revanced.patcher.util.proxy.mutableTypes.MutableAnnotation.Companion.toMutable
import app.revanced.patcher.proxy.mutableTypes.encodedValue.MutableEncodedValue import app.revanced.patcher.util.proxy.mutableTypes.encodedValue.MutableEncodedValue
import app.revanced.patcher.proxy.mutableTypes.encodedValue.MutableEncodedValue.Companion.toMutable import app.revanced.patcher.util.proxy.mutableTypes.encodedValue.MutableEncodedValue.Companion.toMutable
import org.jf.dexlib2.HiddenApiRestriction import org.jf.dexlib2.HiddenApiRestriction
import org.jf.dexlib2.base.reference.BaseFieldReference import org.jf.dexlib2.base.reference.BaseFieldReference
import org.jf.dexlib2.iface.Field import org.jf.dexlib2.iface.Field

View File

@@ -1,7 +1,7 @@
package app.revanced.patcher.proxy.mutableTypes package app.revanced.patcher.util.proxy.mutableTypes
import app.revanced.patcher.proxy.mutableTypes.MutableAnnotation.Companion.toMutable import app.revanced.patcher.util.proxy.mutableTypes.MutableAnnotation.Companion.toMutable
import app.revanced.patcher.proxy.mutableTypes.MutableMethodParameter.Companion.toMutable import app.revanced.patcher.util.proxy.mutableTypes.MutableMethodParameter.Companion.toMutable
import org.jf.dexlib2.HiddenApiRestriction import org.jf.dexlib2.HiddenApiRestriction
import org.jf.dexlib2.base.reference.BaseMethodReference import org.jf.dexlib2.base.reference.BaseMethodReference
import org.jf.dexlib2.builder.MutableMethodImplementation import org.jf.dexlib2.builder.MutableMethodImplementation

View File

@@ -1,6 +1,6 @@
package app.revanced.patcher.proxy.mutableTypes package app.revanced.patcher.util.proxy.mutableTypes
import app.revanced.patcher.proxy.mutableTypes.MutableAnnotation.Companion.toMutable import app.revanced.patcher.util.proxy.mutableTypes.MutableAnnotation.Companion.toMutable
import org.jf.dexlib2.base.BaseMethodParameter import org.jf.dexlib2.base.BaseMethodParameter
import org.jf.dexlib2.iface.MethodParameter import org.jf.dexlib2.iface.MethodParameter

View File

@@ -1,6 +1,6 @@
package app.revanced.patcher.proxy.mutableTypes.encodedValue package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
import app.revanced.patcher.proxy.mutableTypes.MutableAnnotationElement.Companion.toMutable import app.revanced.patcher.util.proxy.mutableTypes.MutableAnnotationElement.Companion.toMutable
import org.jf.dexlib2.base.value.BaseAnnotationEncodedValue import org.jf.dexlib2.base.value.BaseAnnotationEncodedValue
import org.jf.dexlib2.iface.AnnotationElement import org.jf.dexlib2.iface.AnnotationElement
import org.jf.dexlib2.iface.value.AnnotationEncodedValue import org.jf.dexlib2.iface.value.AnnotationEncodedValue

View File

@@ -1,6 +1,6 @@
package app.revanced.patcher.proxy.mutableTypes.encodedValue package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
import app.revanced.patcher.proxy.mutableTypes.encodedValue.MutableEncodedValue.Companion.toMutable import app.revanced.patcher.util.proxy.mutableTypes.encodedValue.MutableEncodedValue.Companion.toMutable
import org.jf.dexlib2.base.value.BaseArrayEncodedValue import org.jf.dexlib2.base.value.BaseArrayEncodedValue
import org.jf.dexlib2.iface.value.ArrayEncodedValue import org.jf.dexlib2.iface.value.ArrayEncodedValue
import org.jf.dexlib2.iface.value.EncodedValue import org.jf.dexlib2.iface.value.EncodedValue

View File

@@ -1,4 +1,4 @@
package app.revanced.patcher.proxy.mutableTypes.encodedValue package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
import org.jf.dexlib2.base.value.BaseBooleanEncodedValue import org.jf.dexlib2.base.value.BaseBooleanEncodedValue
import org.jf.dexlib2.iface.value.BooleanEncodedValue import org.jf.dexlib2.iface.value.BooleanEncodedValue

View File

@@ -1,4 +1,4 @@
package app.revanced.patcher.proxy.mutableTypes.encodedValue package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
import org.jf.dexlib2.base.value.BaseByteEncodedValue import org.jf.dexlib2.base.value.BaseByteEncodedValue
import org.jf.dexlib2.iface.value.ByteEncodedValue import org.jf.dexlib2.iface.value.ByteEncodedValue

View File

@@ -1,4 +1,4 @@
package app.revanced.patcher.proxy.mutableTypes.encodedValue package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
import org.jf.dexlib2.base.value.BaseCharEncodedValue import org.jf.dexlib2.base.value.BaseCharEncodedValue
import org.jf.dexlib2.iface.value.CharEncodedValue import org.jf.dexlib2.iface.value.CharEncodedValue

View File

@@ -1,4 +1,4 @@
package app.revanced.patcher.proxy.mutableTypes.encodedValue package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
import org.jf.dexlib2.base.value.BaseDoubleEncodedValue import org.jf.dexlib2.base.value.BaseDoubleEncodedValue
import org.jf.dexlib2.iface.value.DoubleEncodedValue import org.jf.dexlib2.iface.value.DoubleEncodedValue

View File

@@ -1,4 +1,4 @@
package app.revanced.patcher.proxy.mutableTypes.encodedValue package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
import org.jf.dexlib2.ValueType import org.jf.dexlib2.ValueType
import org.jf.dexlib2.iface.value.* import org.jf.dexlib2.iface.value.*

View File

@@ -1,4 +1,4 @@
package app.revanced.patcher.proxy.mutableTypes.encodedValue package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
import org.jf.dexlib2.base.value.BaseEnumEncodedValue import org.jf.dexlib2.base.value.BaseEnumEncodedValue
import org.jf.dexlib2.iface.reference.FieldReference import org.jf.dexlib2.iface.reference.FieldReference

View File

@@ -1,4 +1,4 @@
package app.revanced.patcher.proxy.mutableTypes.encodedValue package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
import org.jf.dexlib2.ValueType import org.jf.dexlib2.ValueType
import org.jf.dexlib2.base.value.BaseFieldEncodedValue import org.jf.dexlib2.base.value.BaseFieldEncodedValue

View File

@@ -1,4 +1,4 @@
package app.revanced.patcher.proxy.mutableTypes.encodedValue package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
import org.jf.dexlib2.base.value.BaseFloatEncodedValue import org.jf.dexlib2.base.value.BaseFloatEncodedValue
import org.jf.dexlib2.iface.value.FloatEncodedValue import org.jf.dexlib2.iface.value.FloatEncodedValue

View File

@@ -1,4 +1,4 @@
package app.revanced.patcher.proxy.mutableTypes.encodedValue package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
import org.jf.dexlib2.base.value.BaseIntEncodedValue import org.jf.dexlib2.base.value.BaseIntEncodedValue
import org.jf.dexlib2.iface.value.IntEncodedValue import org.jf.dexlib2.iface.value.IntEncodedValue

View File

@@ -1,4 +1,4 @@
package app.revanced.patcher.proxy.mutableTypes.encodedValue package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
import org.jf.dexlib2.base.value.BaseLongEncodedValue import org.jf.dexlib2.base.value.BaseLongEncodedValue
import org.jf.dexlib2.iface.value.LongEncodedValue import org.jf.dexlib2.iface.value.LongEncodedValue

View File

@@ -1,4 +1,4 @@
package app.revanced.patcher.proxy.mutableTypes.encodedValue package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
import org.jf.dexlib2.base.value.BaseMethodEncodedValue import org.jf.dexlib2.base.value.BaseMethodEncodedValue
import org.jf.dexlib2.iface.reference.MethodReference import org.jf.dexlib2.iface.reference.MethodReference

View File

@@ -1,4 +1,4 @@
package app.revanced.patcher.proxy.mutableTypes.encodedValue package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
import org.jf.dexlib2.base.value.BaseMethodHandleEncodedValue import org.jf.dexlib2.base.value.BaseMethodHandleEncodedValue
import org.jf.dexlib2.iface.reference.MethodHandleReference import org.jf.dexlib2.iface.reference.MethodHandleReference

View File

@@ -1,4 +1,4 @@
package app.revanced.patcher.proxy.mutableTypes.encodedValue package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
import org.jf.dexlib2.base.value.BaseMethodTypeEncodedValue import org.jf.dexlib2.base.value.BaseMethodTypeEncodedValue
import org.jf.dexlib2.iface.reference.MethodProtoReference import org.jf.dexlib2.iface.reference.MethodProtoReference

View File

@@ -1,4 +1,4 @@
package app.revanced.patcher.proxy.mutableTypes.encodedValue package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
import org.jf.dexlib2.base.value.BaseNullEncodedValue import org.jf.dexlib2.base.value.BaseNullEncodedValue
import org.jf.dexlib2.iface.value.ByteEncodedValue import org.jf.dexlib2.iface.value.ByteEncodedValue

View File

@@ -1,4 +1,4 @@
package app.revanced.patcher.proxy.mutableTypes.encodedValue package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
import org.jf.dexlib2.base.value.BaseShortEncodedValue import org.jf.dexlib2.base.value.BaseShortEncodedValue
import org.jf.dexlib2.iface.value.ShortEncodedValue import org.jf.dexlib2.iface.value.ShortEncodedValue

View File

@@ -1,4 +1,4 @@
package app.revanced.patcher.proxy.mutableTypes.encodedValue package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
import org.jf.dexlib2.base.value.BaseStringEncodedValue import org.jf.dexlib2.base.value.BaseStringEncodedValue
import org.jf.dexlib2.iface.value.ByteEncodedValue import org.jf.dexlib2.iface.value.ByteEncodedValue

View File

@@ -1,4 +1,4 @@
package app.revanced.patcher.proxy.mutableTypes.encodedValue package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
import org.jf.dexlib2.base.value.BaseTypeEncodedValue import org.jf.dexlib2.base.value.BaseTypeEncodedValue
import org.jf.dexlib2.iface.value.TypeEncodedValue import org.jf.dexlib2.iface.value.TypeEncodedValue

View File

@@ -1,4 +1,4 @@
package app.revanced.patcher.smali package app.revanced.patcher.util.smali
import org.antlr.runtime.CommonTokenStream import org.antlr.runtime.CommonTokenStream
import org.antlr.runtime.TokenSource import org.antlr.runtime.TokenSource
@@ -13,9 +13,9 @@ import org.jf.smali.smaliTreeWalker
import java.io.InputStreamReader import java.io.InputStreamReader
private const val METHOD_TEMPLATE = """ private const val METHOD_TEMPLATE = """
.class public Linlinecompiler; .class LInlineCompiler;
.super Ljava/lang/Object; .super Ljava/lang/Object;
.method public static compiler(%s)V .method %s dummyMethod(%s)V
.registers %d .registers %d
%s %s
.end method .end method
@@ -32,8 +32,13 @@ class InlineSmaliCompiler {
* be messed up and results in broken Dalvik bytecode. * be messed up and results in broken Dalvik bytecode.
* FIXME: Fix the above issue. When this is fixed, add the proper conversions in [InstructionConverter]. * FIXME: Fix the above issue. When this is fixed, add the proper conversions in [InstructionConverter].
*/ */
fun compileMethodInstructions(instructions: String, parameters: String, registers: Int): List<BuilderInstruction> { fun compileMethodInstructions(
val input = METHOD_TEMPLATE.format(parameters, registers, instructions) instructions: String,
parameters: String,
registers: Int,
forStaticMethod: Boolean
): List<BuilderInstruction> {
val input = METHOD_TEMPLATE.format(if (forStaticMethod) "static" else "", parameters, registers, instructions)
val reader = InputStreamReader(input.byteInputStream()) val reader = InputStreamReader(input.byteInputStream())
val lexer: LexerErrorInterface = smaliFlexLexer(reader, 15) val lexer: LexerErrorInterface = smaliFlexLexer(reader, 15)
val tokens = CommonTokenStream(lexer as TokenSource) val tokens = CommonTokenStream(lexer as TokenSource)
@@ -54,5 +59,8 @@ class InlineSmaliCompiler {
} }
} }
fun String.toInstructions(parameters: String = "", registers: Int = 1) = InlineSmaliCompiler.compileMethodInstructions(this, parameters, registers) fun String.toInstructions(parameters: String = "", registers: Int = 1, forStaticMethod: Boolean = true) =
fun String.toInstruction(parameters: String = "", registers: Int = 1) = this.toInstructions(parameters, registers).first() InlineSmaliCompiler.compileMethodInstructions(this, parameters, registers, forStaticMethod)
fun String.toInstruction(parameters: String = "", registers: Int = 1, forStaticMethod: Boolean = true) =
this.toInstructions(parameters, registers, forStaticMethod).first()

View File

@@ -1,4 +1,4 @@
package app.revanced.patcher.smali package app.revanced.patcher.util.smali
import org.jf.dexlib2.Format import org.jf.dexlib2.Format
import org.jf.dexlib2.builder.instruction.* import org.jf.dexlib2.builder.instruction.*

View File

@@ -1,49 +0,0 @@
package app.revanced.patcher
import app.revanced.patcher.signature.PatternScanMethod
import app.revanced.patcher.usage.ExampleBytecodePatch
import app.revanced.patcher.usage.ExampleResourcePatch
import org.junit.jupiter.api.Test
import java.io.File
import kotlin.test.assertTrue
internal class PatcherTest {
@Test
fun testPatcher() {
return // FIXME: create a proper resource to pass this test
val patcher = Patcher(
File(PatcherTest::class.java.getResource("/example.apk")!!.toURI()),
"exampleCacheDirectory",
patchResources = true
)
patcher.addPatches(listOf(ExampleBytecodePatch(), ExampleResourcePatch()))
for (signature in patcher.resolveSignatures()) {
if (!signature.resolved) {
throw Exception("Signature ${signature.metadata.name} was not resolved!")
}
val patternScanMethod = signature.metadata.patternScanMethod
if (patternScanMethod is PatternScanMethod.Fuzzy) {
val warnings = patternScanMethod.warnings
if (warnings != null) {
println("Signature ${signature.metadata.name} had ${warnings.size} warnings!")
for (warning in warnings) {
println(warning.toString())
}
} else {
println("Signature ${signature.metadata.name} used the fuzzy resolver, but the warnings list is null!")
}
}
}
for ((metadata, result) in patcher.applyPatches()) {
if (result.isFailure) {
throw Exception("Patch ${metadata.shortName} failed", result.exceptionOrNull()!!)
} else {
println("Patch ${metadata.shortName} applied successfully!")
}
}
val out = patcher.save()
assertTrue(out.isNotEmpty(), "Expected the output of Patcher#save() to not be empty.")
}
}

View File

@@ -1,50 +0,0 @@
package app.revanced.patcher.usage
import app.revanced.patcher.data.implementation.ResourceData
import app.revanced.patcher.patch.implementation.ResourcePatch
import app.revanced.patcher.patch.implementation.metadata.PatchMetadata
import app.revanced.patcher.patch.implementation.misc.PatchResult
import app.revanced.patcher.patch.implementation.misc.PatchResultSuccess
import org.w3c.dom.Element
class ExampleResourcePatch : ResourcePatch(
PatchMetadata(
"example-patch",
"Example Resource Patch",
"Example demonstration of a resource patch.",
packageMetadata,
"0.0.1"
)
) {
override fun execute(data: ResourceData): PatchResult {
val editor = data.getXmlEditor("AndroidManifest.xml")
// regular DomFileEditor
val element = editor
.file
.getElementsByTagName("application")
.item(0) as Element
element
.setAttribute(
"exampleAttribute",
"exampleValue"
)
// close the editor to write changes
editor.close()
// iterate through all available resources
data.forEach {
if (it.extension.lowercase() != "xml") return@forEach
data.replace(
it.path,
"\\ddip", // regex supported
"0dip",
true
)
}
return PatchResultSuccess()
}
}

View File

@@ -0,0 +1,46 @@
package app.revanced.patcher.usage
import org.junit.jupiter.api.Test
internal class PatcherTest {
@Test
fun testPatcher() {
return // FIXME: create a proper resource to pass this test
/**
val patcher = Patcher(
File(PatcherTest::class.java.getResource("/example.apk")!!.toURI()),
"exampleCacheDirectory",
patchResources = true
)
patcher.addPatches(listOf(ExampleBytecodePatch(), ExampleResourcePatch()))
for (signature in patcher.resolveSignatures()) {
if (!signature.resolved) {
throw Exception("Signature ${signature.metadata.name} was not resolved!")
}
val patternScanMethod = signature.metadata.patternScanMethod
if (patternScanMethod is PatternScanMethod.Fuzzy) {
val warnings = patternScanMethod.warnings
if (warnings != null) {
println("Signature ${signature.metadata.name} had ${warnings.size} warnings!")
for (warning in warnings) {
println(warning.toString())
}
} else {
println("Signature ${signature.metadata.name} used the fuzzy resolver, but the warnings list is null!")
}
}
}
for ((metadata, result) in patcher.applyPatches()) {
if (result.isFailure) {
throw Exception("Patch ${metadata.shortName} failed", result.exceptionOrNull()!!)
} else {
println("Patch ${metadata.shortName} applied successfully!")
}
}
val out = patcher.save()
assertTrue(out.isNotEmpty(), "Expected the output of Patcher#save() to not be empty.")
*/
}
}

View File

@@ -0,0 +1,14 @@
package app.revanced.patcher.usage.bytecode.annotation
import app.revanced.patcher.annotation.Compatibility
import app.revanced.patcher.annotation.Package
@Compatibility(
[Package(
"com.example.examplePackage", arrayOf("0.0.1", "0.0.2")
)]
)
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
internal annotation class ExampleBytecodeCompatibility

View File

@@ -1,21 +1,21 @@
package app.revanced.patcher.usage package app.revanced.patcher.usage.bytecode.patch
import app.revanced.patcher.annotation.Description
import app.revanced.patcher.annotation.Name
import app.revanced.patcher.annotation.Version
import app.revanced.patcher.data.implementation.BytecodeData import app.revanced.patcher.data.implementation.BytecodeData
import app.revanced.patcher.extensions.addInstructions import app.revanced.patcher.extensions.addInstructions
import app.revanced.patcher.extensions.or import app.revanced.patcher.extensions.or
import app.revanced.patcher.patch.annotations.Patch
import app.revanced.patcher.patch.implementation.BytecodePatch import app.revanced.patcher.patch.implementation.BytecodePatch
import app.revanced.patcher.patch.implementation.metadata.PackageMetadata
import app.revanced.patcher.patch.implementation.metadata.PatchMetadata
import app.revanced.patcher.patch.implementation.misc.PatchResult import app.revanced.patcher.patch.implementation.misc.PatchResult
import app.revanced.patcher.patch.implementation.misc.PatchResultSuccess import app.revanced.patcher.patch.implementation.misc.PatchResultSuccess
import app.revanced.patcher.proxy.mutableTypes.MutableField.Companion.toMutable import app.revanced.patcher.usage.bytecode.signatures.ExampleSignature
import app.revanced.patcher.proxy.mutableTypes.MutableMethod.Companion.toMutable import app.revanced.patcher.usage.resource.annotation.ExampleResourceCompatibility
import app.revanced.patcher.signature.MethodMetadata import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable
import app.revanced.patcher.signature.MethodSignature import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
import app.revanced.patcher.signature.MethodSignatureMetadata import app.revanced.patcher.util.smali.toInstruction
import app.revanced.patcher.signature.PatternScanMethod import app.revanced.patcher.util.smali.toInstructions
import app.revanced.patcher.smali.toInstruction
import app.revanced.patcher.smali.toInstructions
import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableList
import org.jf.dexlib2.AccessFlags import org.jf.dexlib2.AccessFlags
import org.jf.dexlib2.Format import org.jf.dexlib2.Format
@@ -32,45 +32,15 @@ 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
val packageMetadata = listOf( @Patch
PackageMetadata( @Name("example-bytecode-patch")
"com.example.examplePackage", @Description("Example demonstration of a bytecode patch.")
listOf("0.0.1", "0.0.2") @ExampleResourceCompatibility
) @Version("0.0.1")
)
class ExampleBytecodePatch : BytecodePatch( class ExampleBytecodePatch : BytecodePatch(
PatchMetadata(
"example-patch", listOf(
"ReVanced example patch", ExampleSignature
"A demonstrative patch to feature the core features of the ReVanced patcher",
packageMetadata,
"0.0.1"
),
setOf(
MethodSignature(
MethodSignatureMetadata(
"Example signature",
MethodMetadata(
"TestClass",
"main",
),
PatternScanMethod.Fuzzy(1),
packageMetadata,
"The main method of TestClass",
"1.0.0"
),
"V",
AccessFlags.PUBLIC or AccessFlags.STATIC,
listOf("[L"),
listOf(
Opcode.SGET_OBJECT,
null, // Testing unknown opcodes.
Opcode.INVOKE_STATIC, // This is intentionally wrong to test the Fuzzy resolver.
Opcode.RETURN_VOID
),
null
)
) )
) { ) {
// This function will be executed by the patcher. // This function will be executed by the patcher.
@@ -85,7 +55,7 @@ class ExampleBytecodePatch : BytecodePatch(
// Let's modify it, so it prints "Hello, ReVanced! Editing bytecode." // Let's modify it, so it prints "Hello, ReVanced! Editing bytecode."
// Get the start index of our opcode pattern. // Get the start index of our opcode pattern.
// This will be the index of the instruction with the opcode CONST_STRING. // This will be the index of the instruction with the opcode CONST_STRING.
val startIndex = result.scanData.startIndex val startIndex = result.scanResult.startIndex
implementation.replaceStringAt(startIndex, "Hello, ReVanced! Editing bytecode.") implementation.replaceStringAt(startIndex, "Hello, ReVanced! Editing bytecode.")

View File

@@ -0,0 +1,32 @@
package app.revanced.patcher.usage.bytecode.signatures
import app.revanced.patcher.annotation.Name
import app.revanced.patcher.annotation.Version
import app.revanced.patcher.extensions.or
import app.revanced.patcher.signature.implementation.method.MethodSignature
import app.revanced.patcher.signature.implementation.method.annotation.FuzzyPatternScanMethod
import app.revanced.patcher.signature.implementation.method.annotation.MatchingMethod
import app.revanced.patcher.usage.bytecode.annotation.ExampleBytecodeCompatibility
import org.jf.dexlib2.AccessFlags
import org.jf.dexlib2.Opcode
@Name("example-signature")
@MatchingMethod(
"LexampleClass;",
"exampleMehod"
)
@FuzzyPatternScanMethod(2)
@ExampleBytecodeCompatibility
@Version("0.0.1")
object ExampleSignature : MethodSignature(
"V",
AccessFlags.PUBLIC or AccessFlags.STATIC,
listOf("[L"),
listOf(
Opcode.SGET_OBJECT,
null, // Testing unknown opcodes.
Opcode.INVOKE_STATIC, // This is intentionally wrong to test the Fuzzy resolver.
Opcode.RETURN_VOID
),
null
)

View File

@@ -0,0 +1,14 @@
package app.revanced.patcher.usage.resource.annotation
import app.revanced.patcher.annotation.Compatibility
import app.revanced.patcher.annotation.Package
@Compatibility(
[Package(
"com.example.examplePackage", arrayOf("0.0.1", "0.0.2")
)]
)
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
internal annotation class ExampleResourceCompatibility

View File

@@ -0,0 +1,47 @@
package app.revanced.patcher.usage.resource.patch
import app.revanced.patcher.annotation.Description
import app.revanced.patcher.annotation.Name
import app.revanced.patcher.annotation.Version
import app.revanced.patcher.data.implementation.ResourceData
import app.revanced.patcher.patch.annotations.Patch
import app.revanced.patcher.patch.implementation.ResourcePatch
import app.revanced.patcher.patch.implementation.misc.PatchResult
import app.revanced.patcher.patch.implementation.misc.PatchResultSuccess
import app.revanced.patcher.usage.resource.annotation.ExampleResourceCompatibility
import org.w3c.dom.Element
@Patch
@Name("example-resource-patch")
@Description("Example demonstration of a resource patch.")
@ExampleResourceCompatibility
@Version("0.0.1")
class ExampleResourcePatch : ResourcePatch() {
override fun execute(data: ResourceData): PatchResult {
data.getXmlEditor("AndroidManifest.xml").use { domFileEditor ->
val element = domFileEditor // regular DomFileEditor
.file
.getElementsByTagName("application")
.item(0) as Element
element
.setAttribute(
"exampleAttribute",
"exampleValue"
)
}
// iterate through all available resources
data.forEach {
if (it.extension.lowercase() != "xml") return@forEach
data.replace(
it.path,
"\\ddip", // regex supported
"0dip",
true
)
}
return PatchResultSuccess()
}
}