You've already forked revanced-patcher
							
							
				mirror of
				https://github.com/revanced/revanced-patcher
				synced 2025-11-02 07:30:52 +01:00 
			
		
		
		
	Compare commits
	
		
			45 Commits
		
	
	
		
			v20.0.1-de
			...
			dependabot
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					0e47873dc1 | ||
| 
						 | 
					3a8b2ba935 | ||
| 
						 | 
					39c5a66ce3 | ||
| 
						 | 
					b160a2adc0 | ||
| 
						 | 
					33fadcbd0c | ||
| 
						 | 
					68db95b99b | ||
| 
						 | 
					4f2ef3c47c | ||
| 
						 | 
					062ae14936 | ||
| 
						 | 
					99f431897e | ||
| 
						 | 
					d80abbcd17 | ||
| 
						 | 
					509ecc81e1 | ||
| 
						 | 
					e4e66b0d8b | ||
| 
						 | 
					bb8771bb8b | ||
| 
						 | 
					754b02e4ca | ||
| 
						 | 
					fe5fb736cb | ||
| 
						 | 
					fc505a8726 | ||
| 
						 | 
					88a3252574 | ||
| 
						 | 
					ead701bdaf | ||
| 
						 | 
					0581dcf931 | ||
| 
						 | 
					62191e3c4a | ||
| 
						 | 
					1358d3fa10 | ||
| 
						 | 
					6712f0ea72 | ||
| 
						 | 
					0746c22743 | ||
| 
						 | 
					7f55868e6f | ||
| 
						 | 
					5d996def4d | ||
| 
						 | 
					49f4570164 | ||
| 
						 | 
					b8249789df | ||
| 
						 | 
					0abf1c6c02 | ||
| 
						 | 
					aa472eb985 | ||
| 
						 | 
					ab624f04f6 | ||
| 
						 | 
					21b5c079fb | ||
| 
						 | 
					5024204046 | ||
| 
						 | 
					a44802ef4e | ||
| 
						 | 
					4c1c34ad01 | ||
| 
						 | 
					b2aecb726d | ||
| 
						 | 
					851f9c7885 | ||
| 
						 | 
					ea6fc70caa | ||
| 
						 | 
					a2875d1d64 | ||
| 
						 | 
					2be6e97817 | ||
| 
						 | 
					348d0070e7 | ||
| 
						 | 
					d53aacdad4 | ||
| 
						 | 
					f1615b7ab5 | ||
| 
						 | 
					ffb1d880d7 | ||
| 
						 | 
					e95f13ae3e | ||
| 
						 | 
					e1b984d601 | 
							
								
								
									
										4
									
								
								.github/workflows/build_pull_request.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/build_pull_request.yml
									
									
									
									
										vendored
									
									
								
							@@ -12,12 +12,12 @@ jobs:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Checkout
 | 
			
		||||
        uses: actions/checkout@v4
 | 
			
		||||
        uses: actions/checkout@v5
 | 
			
		||||
        with:
 | 
			
		||||
          fetch-depth: 0
 | 
			
		||||
 | 
			
		||||
      - name: Cache Gradle
 | 
			
		||||
        uses: burrunan/gradle-cache-action@v1
 | 
			
		||||
        uses: burrunan/gradle-cache-action@v3
 | 
			
		||||
 | 
			
		||||
      - name: Build
 | 
			
		||||
        env:
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/open_pull_request.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/open_pull_request.yml
									
									
									
									
										vendored
									
									
								
							@@ -15,7 +15,7 @@ jobs:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Checkout
 | 
			
		||||
        uses: actions/checkout@v4
 | 
			
		||||
        uses: actions/checkout@v5
 | 
			
		||||
 | 
			
		||||
      - name: Open pull request
 | 
			
		||||
        uses: repo-sync/pull-request@v2
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										11
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										11
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							@@ -10,10 +10,13 @@ on:
 | 
			
		||||
jobs:
 | 
			
		||||
  release:
 | 
			
		||||
    name: Release
 | 
			
		||||
    permissions:
 | 
			
		||||
      contents: write
 | 
			
		||||
      packages: write
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Checkout
 | 
			
		||||
        uses: actions/checkout@v4
 | 
			
		||||
        uses: actions/checkout@v5
 | 
			
		||||
        with:
 | 
			
		||||
          # Make sure the release step uses its own credentials:
 | 
			
		||||
          # https://github.com/cycjimmy/semantic-release-action#private-packages
 | 
			
		||||
@@ -21,7 +24,7 @@ jobs:
 | 
			
		||||
          fetch-depth: 0
 | 
			
		||||
 | 
			
		||||
      - name: Cache Gradle
 | 
			
		||||
        uses: burrunan/gradle-cache-action@v1
 | 
			
		||||
        uses: burrunan/gradle-cache-action@v3
 | 
			
		||||
 | 
			
		||||
      - name: Build
 | 
			
		||||
        env:
 | 
			
		||||
@@ -29,7 +32,7 @@ jobs:
 | 
			
		||||
        run: ./gradlew build clean
 | 
			
		||||
 | 
			
		||||
      - name: Setup Node.js
 | 
			
		||||
        uses: actions/setup-node@v4
 | 
			
		||||
        uses: actions/setup-node@v5
 | 
			
		||||
        with:
 | 
			
		||||
          node-version: "lts/*"
 | 
			
		||||
          cache: 'npm'
 | 
			
		||||
@@ -46,5 +49,5 @@ jobs:
 | 
			
		||||
 | 
			
		||||
      - name: Release
 | 
			
		||||
        env:
 | 
			
		||||
          GITHUB_TOKEN: ${{ secrets.REPOSITORY_PUSH_ACCESS }}
 | 
			
		||||
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
 | 
			
		||||
        run: npm exec semantic-release
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,8 @@
 | 
			
		||||
        "assets": [
 | 
			
		||||
          "CHANGELOG.md",
 | 
			
		||||
          "gradle.properties"
 | 
			
		||||
        ]
 | 
			
		||||
        ],
 | 
			
		||||
        "message": "chore: Release v${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    [
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										158
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										158
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@@ -1,3 +1,161 @@
 | 
			
		||||
# [21.1.0-dev.5](https://github.com/ReVanced/revanced-patcher/compare/v21.1.0-dev.4...v21.1.0-dev.5) (2025-10-16)
 | 
			
		||||
 | 
			
		||||
# [21.1.0-dev.4](https://github.com/ReVanced/revanced-patcher/compare/v21.1.0-dev.3...v21.1.0-dev.4) (2025-07-18)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Bug Fixes
 | 
			
		||||
 | 
			
		||||
* Correctly save XML files in UTF-8 by using a bufferedWriter ([#356](https://github.com/ReVanced/revanced-patcher/issues/356)) ([33fadcb](https://github.com/ReVanced/revanced-patcher/commit/33fadcbd0c7076b848bdca4d62a9c684d5781232))
 | 
			
		||||
 | 
			
		||||
# [21.1.0-dev.3](https://github.com/ReVanced/revanced-patcher/compare/v21.1.0-dev.2...v21.1.0-dev.3) (2025-06-20)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Bug Fixes
 | 
			
		||||
 | 
			
		||||
* Encode XML files as UTF-8 to fix compilation of resources ([#339](https://github.com/ReVanced/revanced-patcher/issues/339)) ([4f2ef3c](https://github.com/ReVanced/revanced-patcher/commit/4f2ef3c47cea76a26c464cfb45d4bb57fe7198b5))
 | 
			
		||||
 | 
			
		||||
# [21.1.0-dev.2](https://github.com/ReVanced/revanced-patcher/compare/v21.1.0-dev.1...v21.1.0-dev.2) (2025-06-20)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Bug Fixes
 | 
			
		||||
 | 
			
		||||
* Add back missing log by naming logger correctly ([#332](https://github.com/ReVanced/revanced-patcher/issues/332)) ([e4e66b0](https://github.com/ReVanced/revanced-patcher/commit/e4e66b0d8bb0986b79fb150b9c15da35b8e11561))
 | 
			
		||||
* Support UTF-8 chars when compiling instructions in Smali in non UTF-8 environments ([#331](https://github.com/ReVanced/revanced-patcher/issues/331)) ([bb8771b](https://github.com/ReVanced/revanced-patcher/commit/bb8771bb8b8ab1724d957e56f4de88c02684d87b))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Features
 | 
			
		||||
 | 
			
		||||
* Use option name as key for simplicity and consistency ([754b02e](https://github.com/ReVanced/revanced-patcher/commit/754b02e4ca66ec10764d5205c6643f2d86d0c6a2))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Performance Improvements
 | 
			
		||||
 | 
			
		||||
* Use a buffered writer to reduce IO overhead ([#347](https://github.com/ReVanced/revanced-patcher/issues/347)) ([99f4318](https://github.com/ReVanced/revanced-patcher/commit/99f431897eb9e607987fd5d09b879d7eda442f3e))
 | 
			
		||||
 | 
			
		||||
# [21.1.0-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v21.0.0...v21.1.0-dev.1) (2024-12-07)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Features
 | 
			
		||||
 | 
			
		||||
* Add identity hash code to unnamed patches ([88a3252](https://github.com/ReVanced/revanced-patcher/commit/88a325257494939a79fb30dd51d60c5c52546755))
 | 
			
		||||
 | 
			
		||||
# [21.0.0](https://github.com/ReVanced/revanced-patcher/compare/v20.0.2...v21.0.0) (2024-11-05)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Bug Fixes
 | 
			
		||||
 | 
			
		||||
* Match fingerprint before delegating the match property ([5d996de](https://github.com/ReVanced/revanced-patcher/commit/5d996def4d3de4e2bfc34562e5a6c7d89a8cddf0))
 | 
			
		||||
* Merge extension only when patch executes ([#315](https://github.com/ReVanced/revanced-patcher/issues/315)) ([aa472eb](https://github.com/ReVanced/revanced-patcher/commit/aa472eb9857145b53b49f843406a9764fbb7e5ce))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Features
 | 
			
		||||
 | 
			
		||||
* Improve Fingerprint API ([#316](https://github.com/ReVanced/revanced-patcher/issues/316)) ([0abf1c6](https://github.com/ReVanced/revanced-patcher/commit/0abf1c6c0279708fdef5cb66b141d07d17682693))
 | 
			
		||||
* Improve various APIs  ([#317](https://github.com/ReVanced/revanced-patcher/issues/317)) ([b824978](https://github.com/ReVanced/revanced-patcher/commit/b8249789df8b90129f7b7ad0e523a8d0ceaab848))
 | 
			
		||||
* Move fingerprint match members to fingerprint for ease of access by using context receivers ([0746c22](https://github.com/ReVanced/revanced-patcher/commit/0746c22743a9561bae2284d234b151f2f8511ca5))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Performance Improvements
 | 
			
		||||
 | 
			
		||||
* Use smallest lookup map for strings ([1358d3f](https://github.com/ReVanced/revanced-patcher/commit/1358d3fa10cb8ba011b6b89cfe3684ecf9849d2f))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### BREAKING CHANGES
 | 
			
		||||
 | 
			
		||||
* Various APIs have been changed.
 | 
			
		||||
* Many APIs have been changed.
 | 
			
		||||
 | 
			
		||||
# [21.0.0-dev.4](https://github.com/ReVanced/revanced-patcher/compare/v21.0.0-dev.3...v21.0.0-dev.4) (2024-11-05)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Performance Improvements
 | 
			
		||||
 | 
			
		||||
* Use smallest lookup map for strings ([1358d3f](https://github.com/ReVanced/revanced-patcher/commit/1358d3fa10cb8ba011b6b89cfe3684ecf9849d2f))
 | 
			
		||||
 | 
			
		||||
# [21.0.0-dev.3](https://github.com/ReVanced/revanced-patcher/compare/v21.0.0-dev.2...v21.0.0-dev.3) (2024-11-05)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Features
 | 
			
		||||
 | 
			
		||||
* Move fingerprint match members to fingerprint for ease of access by using context receivers ([0746c22](https://github.com/ReVanced/revanced-patcher/commit/0746c22743a9561bae2284d234b151f2f8511ca5))
 | 
			
		||||
 | 
			
		||||
# [21.0.0-dev.2](https://github.com/ReVanced/revanced-patcher/compare/v21.0.0-dev.1...v21.0.0-dev.2) (2024-11-01)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Bug Fixes
 | 
			
		||||
 | 
			
		||||
* Match fingerprint before delegating the match property ([5d996de](https://github.com/ReVanced/revanced-patcher/commit/5d996def4d3de4e2bfc34562e5a6c7d89a8cddf0))
 | 
			
		||||
 | 
			
		||||
# [21.0.0-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v20.0.2...v21.0.0-dev.1) (2024-10-27)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Bug Fixes
 | 
			
		||||
 | 
			
		||||
* Merge extension only when patch executes ([#315](https://github.com/ReVanced/revanced-patcher/issues/315)) ([aa472eb](https://github.com/ReVanced/revanced-patcher/commit/aa472eb9857145b53b49f843406a9764fbb7e5ce))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Features
 | 
			
		||||
 | 
			
		||||
* Improve Fingerprint API ([#316](https://github.com/ReVanced/revanced-patcher/issues/316)) ([0abf1c6](https://github.com/ReVanced/revanced-patcher/commit/0abf1c6c0279708fdef5cb66b141d07d17682693))
 | 
			
		||||
* Improve various APIs  ([#317](https://github.com/ReVanced/revanced-patcher/issues/317)) ([b824978](https://github.com/ReVanced/revanced-patcher/commit/b8249789df8b90129f7b7ad0e523a8d0ceaab848))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### BREAKING CHANGES
 | 
			
		||||
 | 
			
		||||
* Various APIs have been changed.
 | 
			
		||||
* Many APIs have been changed.
 | 
			
		||||
 | 
			
		||||
## [20.0.2](https://github.com/ReVanced/revanced-patcher/compare/v20.0.1...v20.0.2) (2024-10-17)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Bug Fixes
 | 
			
		||||
 | 
			
		||||
* Make it work on Android 12 and lower by using existing APIs ([#312](https://github.com/ReVanced/revanced-patcher/issues/312)) ([a44802e](https://github.com/ReVanced/revanced-patcher/commit/a44802ef4ebf59ae47213854ba761c81dadc51f3))
 | 
			
		||||
 | 
			
		||||
## [20.0.2-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v20.0.1...v20.0.2-dev.1) (2024-10-15)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Bug Fixes
 | 
			
		||||
 | 
			
		||||
* Make it work on Android 12 and lower by using existing APIs ([#312](https://github.com/ReVanced/revanced-patcher/issues/312)) ([a44802e](https://github.com/ReVanced/revanced-patcher/commit/a44802ef4ebf59ae47213854ba761c81dadc51f3))
 | 
			
		||||
 | 
			
		||||
## [20.0.1](https://github.com/ReVanced/revanced-patcher/compare/v20.0.0...v20.0.1) (2024-10-13)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Bug Fixes
 | 
			
		||||
 | 
			
		||||
* Check for class type exactly instead of with contains ([#310](https://github.com/ReVanced/revanced-patcher/issues/310)) ([69f2f20](https://github.com/ReVanced/revanced-patcher/commit/69f2f20fd99162f91cd9c531dfe47d00d3152ead))
 | 
			
		||||
* Make it work on Android by not using APIs from JVM unavailable to Android. ([2be6e97](https://github.com/ReVanced/revanced-patcher/commit/2be6e97817437f40e17893dfff3bea2cd4c3ff9e))
 | 
			
		||||
* Use non-nullable type for options ([ea6fc70](https://github.com/ReVanced/revanced-patcher/commit/ea6fc70caab055251ad4d0d3f1b5cf53865abb85))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Performance Improvements
 | 
			
		||||
 | 
			
		||||
* Free memory earlier and remove negligible lookup maps ([d53aacd](https://github.com/ReVanced/revanced-patcher/commit/d53aacdad4ed3750ddae526fb307577ea36e6171))
 | 
			
		||||
 | 
			
		||||
## [20.0.1-dev.5](https://github.com/ReVanced/revanced-patcher/compare/v20.0.1-dev.4...v20.0.1-dev.5) (2024-10-11)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Bug Fixes
 | 
			
		||||
 | 
			
		||||
* Use non-nullable type for options ([ea6fc70](https://github.com/ReVanced/revanced-patcher/commit/ea6fc70caab055251ad4d0d3f1b5cf53865abb85))
 | 
			
		||||
 | 
			
		||||
## [20.0.1-dev.4](https://github.com/ReVanced/revanced-patcher/compare/v20.0.1-dev.3...v20.0.1-dev.4) (2024-10-07)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Bug Fixes
 | 
			
		||||
 | 
			
		||||
* Make it work on Android by not using APIs from JVM unavailable to Android. ([2be6e97](https://github.com/ReVanced/revanced-patcher/commit/2be6e97817437f40e17893dfff3bea2cd4c3ff9e))
 | 
			
		||||
 | 
			
		||||
## [20.0.1-dev.3](https://github.com/ReVanced/revanced-patcher/compare/v20.0.1-dev.2...v20.0.1-dev.3) (2024-10-03)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Performance Improvements
 | 
			
		||||
 | 
			
		||||
* Free memory earlier and remove negligible lookup maps ([d53aacd](https://github.com/ReVanced/revanced-patcher/commit/d53aacdad4ed3750ddae526fb307577ea36e6171))
 | 
			
		||||
 | 
			
		||||
## [20.0.1-dev.2](https://github.com/ReVanced/revanced-patcher/compare/v20.0.1-dev.1...v20.0.1-dev.2) (2024-10-01)
 | 
			
		||||
 | 
			
		||||
## [20.0.1-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v20.0.0...v20.0.1-dev.1) (2024-09-18)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,22 @@
 | 
			
		||||
public final class app/revanced/patcher/Fingerprint {
 | 
			
		||||
	public final fun getMatch ()Lapp/revanced/patcher/Match;
 | 
			
		||||
	public final fun match (Lapp/revanced/patcher/patch/BytecodePatchContext;Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Z
 | 
			
		||||
	public final fun match (Lapp/revanced/patcher/patch/BytecodePatchContext;Lcom/android/tools/smali/dexlib2/iface/Method;)Z
 | 
			
		||||
	public final fun getClassDef (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableClass;
 | 
			
		||||
	public final fun getClassDefOrNull (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableClass;
 | 
			
		||||
	public final fun getMethod (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
 | 
			
		||||
	public final fun getMethodOrNull (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
 | 
			
		||||
	public final fun getOriginalClassDef (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lcom/android/tools/smali/dexlib2/iface/ClassDef;
 | 
			
		||||
	public final fun getOriginalClassDefOrNull (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lcom/android/tools/smali/dexlib2/iface/ClassDef;
 | 
			
		||||
	public final fun getOriginalMethod (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lcom/android/tools/smali/dexlib2/iface/Method;
 | 
			
		||||
	public final fun getOriginalMethodOrNull (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lcom/android/tools/smali/dexlib2/iface/Method;
 | 
			
		||||
	public final fun getPatternMatch (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lapp/revanced/patcher/Match$PatternMatch;
 | 
			
		||||
	public final fun getPatternMatchOrNull (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lapp/revanced/patcher/Match$PatternMatch;
 | 
			
		||||
	public final fun getStringMatches (Lapp/revanced/patcher/patch/BytecodePatchContext;)Ljava/util/List;
 | 
			
		||||
	public final fun getStringMatchesOrNull (Lapp/revanced/patcher/patch/BytecodePatchContext;)Ljava/util/List;
 | 
			
		||||
	public final fun match (Lapp/revanced/patcher/patch/BytecodePatchContext;Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Lapp/revanced/patcher/Match;
 | 
			
		||||
	public final fun match (Lapp/revanced/patcher/patch/BytecodePatchContext;Lcom/android/tools/smali/dexlib2/iface/Method;)Lapp/revanced/patcher/Match;
 | 
			
		||||
	public final fun match (Lapp/revanced/patcher/patch/BytecodePatchContext;Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Lapp/revanced/patcher/Match;
 | 
			
		||||
	public final fun matchOrNull (Lapp/revanced/patcher/patch/BytecodePatchContext;Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Lapp/revanced/patcher/Match;
 | 
			
		||||
	public final fun matchOrNull (Lapp/revanced/patcher/patch/BytecodePatchContext;Lcom/android/tools/smali/dexlib2/iface/Method;)Lapp/revanced/patcher/Match;
 | 
			
		||||
	public final fun matchOrNull (Lapp/revanced/patcher/patch/BytecodePatchContext;Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Lapp/revanced/patcher/Match;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public final class app/revanced/patcher/FingerprintBuilder {
 | 
			
		||||
@@ -18,32 +33,27 @@ public final class app/revanced/patcher/FingerprintBuilder {
 | 
			
		||||
 | 
			
		||||
public final class app/revanced/patcher/FingerprintKt {
 | 
			
		||||
	public static final fun fingerprint (ILkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/Fingerprint;
 | 
			
		||||
	public static final fun fingerprint (Lapp/revanced/patcher/patch/BytecodePatchBuilder;ILkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/patch/BytecodePatchBuilder$InvokedFingerprint;
 | 
			
		||||
	public static synthetic fun fingerprint$default (ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/Fingerprint;
 | 
			
		||||
	public static synthetic fun fingerprint$default (Lapp/revanced/patcher/patch/BytecodePatchBuilder;ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/patch/BytecodePatchBuilder$InvokedFingerprint;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public abstract interface annotation class app/revanced/patcher/InternalApi : java/lang/annotation/Annotation {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public final class app/revanced/patcher/Match {
 | 
			
		||||
	public fun <init> (Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/iface/ClassDef;Lapp/revanced/patcher/Match$PatternMatch;Ljava/util/List;Lapp/revanced/patcher/patch/BytecodePatchContext;)V
 | 
			
		||||
	public final fun getClassDef ()Lcom/android/tools/smali/dexlib2/iface/ClassDef;
 | 
			
		||||
	public final fun getMethod ()Lcom/android/tools/smali/dexlib2/iface/Method;
 | 
			
		||||
	public final fun getMutableClass ()Lapp/revanced/patcher/util/proxy/mutableTypes/MutableClass;
 | 
			
		||||
	public final fun getMutableMethod ()Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
 | 
			
		||||
	public final fun getClassDef ()Lapp/revanced/patcher/util/proxy/mutableTypes/MutableClass;
 | 
			
		||||
	public final fun getMethod ()Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
 | 
			
		||||
	public final fun getOriginalClassDef ()Lcom/android/tools/smali/dexlib2/iface/ClassDef;
 | 
			
		||||
	public final fun getOriginalMethod ()Lcom/android/tools/smali/dexlib2/iface/Method;
 | 
			
		||||
	public final fun getPatternMatch ()Lapp/revanced/patcher/Match$PatternMatch;
 | 
			
		||||
	public final fun getStringMatches ()Ljava/util/List;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public final class app/revanced/patcher/Match$PatternMatch {
 | 
			
		||||
	public fun <init> (II)V
 | 
			
		||||
	public final fun getEndIndex ()I
 | 
			
		||||
	public final fun getStartIndex ()I
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public final class app/revanced/patcher/Match$StringMatch {
 | 
			
		||||
	public fun <init> (Ljava/lang/String;I)V
 | 
			
		||||
	public final fun getIndex ()I
 | 
			
		||||
	public final fun getString ()Ljava/lang/String;
 | 
			
		||||
}
 | 
			
		||||
@@ -63,8 +73,10 @@ public final class app/revanced/patcher/Patcher : java/io/Closeable {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public final class app/revanced/patcher/PatcherConfig {
 | 
			
		||||
	public fun <init> (Ljava/io/File;Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Z)V
 | 
			
		||||
	public synthetic fun <init> (Ljava/io/File;Ljava/io/File;Ljava/lang/String;Ljava/lang/String;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V
 | 
			
		||||
	public fun <init> (Ljava/io/File;Ljava/io/File;Ljava/io/File;Ljava/lang/String;)V
 | 
			
		||||
	public synthetic fun <init> (Ljava/io/File;Ljava/io/File;Ljava/io/File;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
 | 
			
		||||
	public fun <init> (Ljava/io/File;Ljava/io/File;Ljava/lang/String;Ljava/lang/String;)V
 | 
			
		||||
	public synthetic fun <init> (Ljava/io/File;Ljava/io/File;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public final class app/revanced/patcher/PatcherContext : java/io/Closeable {
 | 
			
		||||
@@ -135,40 +147,36 @@ public final class app/revanced/patcher/extensions/InstructionExtensions {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public final class app/revanced/patcher/patch/BytecodePatch : app/revanced/patcher/patch/Patch {
 | 
			
		||||
	public final fun getExtension ()Ljava/io/InputStream;
 | 
			
		||||
	public final fun getFingerprints ()Ljava/util/Set;
 | 
			
		||||
	public final fun getExtensionInputStream ()Ljava/util/function/Supplier;
 | 
			
		||||
	public fun toString ()Ljava/lang/String;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public final class app/revanced/patcher/patch/BytecodePatchBuilder : app/revanced/patcher/patch/PatchBuilder {
 | 
			
		||||
	public synthetic fun build$revanced_patcher ()Lapp/revanced/patcher/patch/Patch;
 | 
			
		||||
	public final fun extendWith (Ljava/lang/String;)Lapp/revanced/patcher/patch/BytecodePatchBuilder;
 | 
			
		||||
	public final fun getExtension ()Ljava/io/InputStream;
 | 
			
		||||
	public final fun invoke (Lapp/revanced/patcher/Fingerprint;)Lapp/revanced/patcher/patch/BytecodePatchBuilder$InvokedFingerprint;
 | 
			
		||||
	public final fun setExtension (Ljava/io/InputStream;)V
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public final class app/revanced/patcher/patch/BytecodePatchBuilder$InvokedFingerprint {
 | 
			
		||||
	public final fun getValue (Ljava/lang/Void;Lkotlin/reflect/KProperty;)Lapp/revanced/patcher/Match;
 | 
			
		||||
	public final fun getExtensionInputStream ()Ljava/util/function/Supplier;
 | 
			
		||||
	public final fun setExtensionInputStream (Ljava/util/function/Supplier;)V
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public final class app/revanced/patcher/patch/BytecodePatchContext : app/revanced/patcher/patch/PatchContext, java/io/Closeable {
 | 
			
		||||
	public final fun classBy (Lkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/util/proxy/ClassProxy;
 | 
			
		||||
	public final fun classByType (Ljava/lang/String;)Lapp/revanced/patcher/util/proxy/ClassProxy;
 | 
			
		||||
	public fun close ()V
 | 
			
		||||
	public synthetic fun get ()Ljava/lang/Object;
 | 
			
		||||
	public fun get ()Ljava/util/Set;
 | 
			
		||||
	public final fun getClasses ()Lapp/revanced/patcher/util/ProxyClassList;
 | 
			
		||||
	public final fun navigate (Lcom/android/tools/smali/dexlib2/iface/Method;)Lapp/revanced/patcher/util/MethodNavigator;
 | 
			
		||||
	public final fun navigate (Lcom/android/tools/smali/dexlib2/iface/reference/MethodReference;)Lapp/revanced/patcher/util/MethodNavigator;
 | 
			
		||||
	public final fun proxy (Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Lapp/revanced/patcher/util/proxy/ClassProxy;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public final class app/revanced/patcher/patch/Option {
 | 
			
		||||
	public fun <init> (Ljava/lang/String;Ljava/lang/Object;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/reflect/KType;Lkotlin/jvm/functions/Function2;)V
 | 
			
		||||
	public synthetic fun <init> (Ljava/lang/String;Ljava/lang/Object;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/reflect/KType;Lkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
 | 
			
		||||
	public fun <init> (Ljava/lang/String;Ljava/lang/Object;Ljava/util/Map;Ljava/lang/String;ZLkotlin/reflect/KType;Lkotlin/jvm/functions/Function2;)V
 | 
			
		||||
	public synthetic fun <init> (Ljava/lang/String;Ljava/lang/Object;Ljava/util/Map;Ljava/lang/String;ZLkotlin/reflect/KType;Lkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
 | 
			
		||||
	public final fun getDefault ()Ljava/lang/Object;
 | 
			
		||||
	public final fun getDescription ()Ljava/lang/String;
 | 
			
		||||
	public final fun getKey ()Ljava/lang/String;
 | 
			
		||||
	public final fun getName ()Ljava/lang/String;
 | 
			
		||||
	public final fun getRequired ()Z
 | 
			
		||||
	public final fun getTitle ()Ljava/lang/String;
 | 
			
		||||
	public final fun getType ()Lkotlin/reflect/KType;
 | 
			
		||||
@@ -286,7 +294,7 @@ public final class app/revanced/patcher/patch/Options : java/util/Map, kotlin/jv
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public abstract class app/revanced/patcher/patch/Patch {
 | 
			
		||||
	public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;ZLjava/util/Set;Ljava/util/Set;Ljava/util/Set;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
 | 
			
		||||
	public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;ZLjava/util/Set;Ljava/util/Set;Ljava/util/Set;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
 | 
			
		||||
	public final fun execute (Lapp/revanced/patcher/patch/PatchContext;)V
 | 
			
		||||
	public final fun finalize (Lapp/revanced/patcher/patch/PatchContext;)V
 | 
			
		||||
	public final fun getCompatiblePackages ()Ljava/util/Set;
 | 
			
		||||
@@ -303,13 +311,13 @@ public abstract class app/revanced/patcher/patch/PatchBuilder {
 | 
			
		||||
	public final fun compatibleWith ([Ljava/lang/String;)V
 | 
			
		||||
	public final fun compatibleWith ([Lkotlin/Pair;)V
 | 
			
		||||
	public final fun dependsOn ([Lapp/revanced/patcher/patch/Patch;)V
 | 
			
		||||
	public final fun execute (Lkotlin/jvm/functions/Function2;)V
 | 
			
		||||
	public final fun finalize (Lkotlin/jvm/functions/Function2;)V
 | 
			
		||||
	public final fun execute (Lkotlin/jvm/functions/Function1;)V
 | 
			
		||||
	public final fun finalize (Lkotlin/jvm/functions/Function1;)V
 | 
			
		||||
	protected final fun getCompatiblePackages ()Ljava/util/Set;
 | 
			
		||||
	protected final fun getDependencies ()Ljava/util/Set;
 | 
			
		||||
	protected final fun getDescription ()Ljava/lang/String;
 | 
			
		||||
	protected final fun getExecutionBlock ()Lkotlin/jvm/functions/Function2;
 | 
			
		||||
	protected final fun getFinalizeBlock ()Lkotlin/jvm/functions/Function2;
 | 
			
		||||
	protected final fun getExecutionBlock ()Lkotlin/jvm/functions/Function1;
 | 
			
		||||
	protected final fun getFinalizeBlock ()Lkotlin/jvm/functions/Function1;
 | 
			
		||||
	protected final fun getName ()Ljava/lang/String;
 | 
			
		||||
	protected final fun getOptions ()Ljava/util/Set;
 | 
			
		||||
	protected final fun getUse ()Z
 | 
			
		||||
@@ -317,8 +325,8 @@ public abstract class app/revanced/patcher/patch/PatchBuilder {
 | 
			
		||||
	public final fun invoke (Ljava/lang/String;[Ljava/lang/String;)Lkotlin/Pair;
 | 
			
		||||
	protected final fun setCompatiblePackages (Ljava/util/Set;)V
 | 
			
		||||
	protected final fun setDependencies (Ljava/util/Set;)V
 | 
			
		||||
	protected final fun setExecutionBlock (Lkotlin/jvm/functions/Function2;)V
 | 
			
		||||
	protected final fun setFinalizeBlock (Lkotlin/jvm/functions/Function2;)V
 | 
			
		||||
	protected final fun setExecutionBlock (Lkotlin/jvm/functions/Function1;)V
 | 
			
		||||
	protected final fun setFinalizeBlock (Lkotlin/jvm/functions/Function1;)V
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public abstract interface class app/revanced/patcher/patch/PatchContext : java/util/function/Supplier {
 | 
			
		||||
@@ -395,18 +403,13 @@ public final class app/revanced/patcher/patch/ResourcePatchBuilder : app/revance
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public final class app/revanced/patcher/patch/ResourcePatchContext : app/revanced/patcher/patch/PatchContext {
 | 
			
		||||
	public final fun delete (Ljava/lang/String;)Z
 | 
			
		||||
	public final fun document (Ljava/io/InputStream;)Lapp/revanced/patcher/util/Document;
 | 
			
		||||
	public final fun document (Ljava/lang/String;)Lapp/revanced/patcher/util/Document;
 | 
			
		||||
	public fun get ()Lapp/revanced/patcher/PatcherResult$PatchedResources;
 | 
			
		||||
	public synthetic fun get ()Ljava/lang/Object;
 | 
			
		||||
	public final fun get (Ljava/lang/String;Z)Ljava/io/File;
 | 
			
		||||
	public static synthetic fun get$default (Lapp/revanced/patcher/patch/ResourcePatchContext;Ljava/lang/String;ZILjava/lang/Object;)Ljava/io/File;
 | 
			
		||||
	public final fun getDocument ()Lapp/revanced/patcher/patch/ResourcePatchContext$DocumentOperatable;
 | 
			
		||||
	public final fun stageDelete (Lkotlin/jvm/functions/Function1;)Z
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public final class app/revanced/patcher/patch/ResourcePatchContext$DocumentOperatable {
 | 
			
		||||
	public fun <init> (Lapp/revanced/patcher/patch/ResourcePatchContext;)V
 | 
			
		||||
	public final fun get (Ljava/io/InputStream;)Lapp/revanced/patcher/util/Document;
 | 
			
		||||
	public final fun get (Ljava/lang/String;)Lapp/revanced/patcher/util/Document;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public final class app/revanced/patcher/util/Document : java/io/Closeable, org/w3c/dom/Document {
 | 
			
		||||
@@ -482,11 +485,12 @@ public final class app/revanced/patcher/util/Document : java/io/Closeable, org/w
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public final class app/revanced/patcher/util/MethodNavigator {
 | 
			
		||||
	public final fun at (ILkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/util/MethodNavigator;
 | 
			
		||||
	public final fun at ([I)Lapp/revanced/patcher/util/MethodNavigator;
 | 
			
		||||
	public static synthetic fun at$default (Lapp/revanced/patcher/util/MethodNavigator;ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/util/MethodNavigator;
 | 
			
		||||
	public final fun immutable ()Lcom/android/tools/smali/dexlib2/iface/Method;
 | 
			
		||||
	public final fun mutable ()Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
 | 
			
		||||
	public final fun getValue (Ljava/lang/Void;Lkotlin/reflect/KProperty;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
 | 
			
		||||
	public final fun original ()Lcom/android/tools/smali/dexlib2/iface/Method;
 | 
			
		||||
	public final fun stop ()Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
 | 
			
		||||
	public final fun to (ILkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/util/MethodNavigator;
 | 
			
		||||
	public final fun to ([I)Lapp/revanced/patcher/util/MethodNavigator;
 | 
			
		||||
	public static synthetic fun to$default (Lapp/revanced/patcher/util/MethodNavigator;ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/util/MethodNavigator;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public final class app/revanced/patcher/util/ProxyClassList : java/util/List, kotlin/jvm/internal/markers/KMutableList {
 | 
			
		||||
 
 | 
			
		||||
@@ -56,6 +56,8 @@ dependencies {
 | 
			
		||||
kotlin {
 | 
			
		||||
    compilerOptions {
 | 
			
		||||
        jvmTarget.set(JvmTarget.JVM_11)
 | 
			
		||||
 | 
			
		||||
        freeCompilerArgs = listOf("-Xcontext-receivers")
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -89,9 +89,9 @@ val patcherResult = Patcher(PatcherConfig(apkFile = File("some.apk"))).use { pat
 | 
			
		||||
    runBlocking {
 | 
			
		||||
        patcher().collect { patchResult ->
 | 
			
		||||
            if (patchResult.exception != null)
 | 
			
		||||
                logger.info("\"${patchResult.patch}\" failed:\n${patchResult.exception}")
 | 
			
		||||
                logger.info { "\"${patchResult.patch}\" failed:\n${patchResult.exception}" }
 | 
			
		||||
            else
 | 
			
		||||
                logger.info("\"${patchResult.patch}\" succeeded")
 | 
			
		||||
                logger.info { "\"${patchResult.patch}\" succeeded" }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -72,6 +72,10 @@ To start developing patches with ReVanced Patcher, you must prepare a developmen
 | 
			
		||||
 | 
			
		||||
Throughout the documentation, [ReVanced Patches](https://github.com/revanced/revanced-patches) will be used as an example project.
 | 
			
		||||
 | 
			
		||||
> [!NOTE]
 | 
			
		||||
> To start a fresh project, 
 | 
			
		||||
> you can use the [ReVanced Patches template](https://github.com/revanced/revanced-patches-template).
 | 
			
		||||
 | 
			
		||||
1. Clone the repository
 | 
			
		||||
 | 
			
		||||
   ```bash
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -73,26 +73,26 @@ package app.revanced.patches.ads
 | 
			
		||||
val disableAdsPatch = bytecodePatch(
 | 
			
		||||
    name = "Disable ads",
 | 
			
		||||
    description = "Disable ads in the app.",
 | 
			
		||||
) { 
 | 
			
		||||
) {
 | 
			
		||||
    compatibleWith("com.some.app"("1.0.0"))
 | 
			
		||||
    
 | 
			
		||||
    // Resource patch disables ads by patching resource files.
 | 
			
		||||
 | 
			
		||||
    // Patches can depend on other patches, executing them first.
 | 
			
		||||
    dependsOn(disableAdsResourcePatch)
 | 
			
		||||
 | 
			
		||||
    // Precompiled DEX file to be merged into the patched app.
 | 
			
		||||
    // Merge precompiled DEX files into the patched app, before the patch is executed.
 | 
			
		||||
    extendWith("disable-ads.rve")
 | 
			
		||||
 | 
			
		||||
    // Fingerprint to find the method to patch.
 | 
			
		||||
    val showAdsMatch by showAdsFingerprint {
 | 
			
		||||
        // More about fingerprints on the next page of the documentation.
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // Business logic of the patch to disable ads in the app.
 | 
			
		||||
    execute {
 | 
			
		||||
        // Fingerprint to find the method to patch.
 | 
			
		||||
        val showAdsFingerprint = fingerprint {
 | 
			
		||||
            // More about fingerprints on the next page of the documentation.
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // In the method that shows ads,
 | 
			
		||||
        // call DisableAdsPatch.shouldDisableAds() from the extension (precompiled DEX file)
 | 
			
		||||
        // to enable or disable ads.
 | 
			
		||||
        showAdsMatch.mutableMethod.addInstructions(
 | 
			
		||||
        showAdsFingerprint.method.addInstructions(
 | 
			
		||||
            0,
 | 
			
		||||
            """
 | 
			
		||||
                invoke-static {}, LDisableAdsPatch;->shouldDisableAds()Z
 | 
			
		||||
@@ -122,11 +122,11 @@ To define an option, use the available `option` functions:
 | 
			
		||||
```kt
 | 
			
		||||
val patch = bytecodePatch(name = "Patch") {
 | 
			
		||||
    // Add an inbuilt option and delegate it to a property.
 | 
			
		||||
    val value by stringOption(key = "option")
 | 
			
		||||
    val value by stringOption(name = "Inbuilt option")
 | 
			
		||||
 | 
			
		||||
    // Add an option with a custom type and delegate it to a property.
 | 
			
		||||
    val string by option<String>(key = "string")
 | 
			
		||||
    
 | 
			
		||||
    val string by option<String>(name = "String option")
 | 
			
		||||
 | 
			
		||||
    execute {
 | 
			
		||||
        println(value)
 | 
			
		||||
        println(string)
 | 
			
		||||
@@ -139,20 +139,20 @@ Options of a patch can be set after loading the patches with `PatchLoader` by ob
 | 
			
		||||
```kt
 | 
			
		||||
loadPatchesJar(patches).apply {
 | 
			
		||||
    // Type is checked at runtime.
 | 
			
		||||
    first { it.name == "Patch" }.options["option"] = "Value"
 | 
			
		||||
    first { it.name == "Patch" }.options["Option"] = "Value"
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
The type of an option can be obtained from the `type` property of the option:
 | 
			
		||||
 | 
			
		||||
```kt
 | 
			
		||||
option.type // The KType of the option.
 | 
			
		||||
option.type // The KType of the option. Captures the full type information of the option.
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Options can be declared outside of a patch and added to a patch manually:
 | 
			
		||||
Options can be declared outside a patch and added to a patch manually:
 | 
			
		||||
 | 
			
		||||
```kt
 | 
			
		||||
val option = stringOption(key = "option")
 | 
			
		||||
val option = stringOption(name = "Option")
 | 
			
		||||
 | 
			
		||||
bytecodePatch(name = "Patch") {
 | 
			
		||||
    val value by option()
 | 
			
		||||
@@ -183,20 +183,18 @@ and use it in a patch:
 | 
			
		||||
```kt
 | 
			
		||||
val patch = bytecodePatch(name = "Complex patch") {
 | 
			
		||||
    extendWith("complex-patch.rve")
 | 
			
		||||
    
 | 
			
		||||
    val match by methodFingerprint()
 | 
			
		||||
    
 | 
			
		||||
    execute { 
 | 
			
		||||
        match.mutableMethod.addInstructions(0, "invoke-static { }, LComplexPatch;->doSomething()V")
 | 
			
		||||
 | 
			
		||||
    execute {
 | 
			
		||||
        fingerprint.method.addInstructions(0, "invoke-static { }, LComplexPatch;->doSomething()V")
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
ReVanced Patcher merges the classes from the extension into `context.classes` before executing the patch. 
 | 
			
		||||
ReVanced Patcher merges the classes from the extension into `context.classes` before executing the patch.
 | 
			
		||||
When the patch is executed, it can reference the classes and methods from the extension.
 | 
			
		||||
 | 
			
		||||
> [!NOTE]
 | 
			
		||||
> 
 | 
			
		||||
>
 | 
			
		||||
> The [ReVanced Patches template](https://github.com/ReVanced/revanced-patches-template) repository
 | 
			
		||||
> is a template project to create patches and extensions.
 | 
			
		||||
 | 
			
		||||
@@ -213,9 +211,9 @@ A simple real-world example would be a patch that opens a resource file of the a
 | 
			
		||||
Other patches that depend on this patch can write to the file, and the finalization block can close the file.
 | 
			
		||||
 | 
			
		||||
```kt
 | 
			
		||||
val patch = bytecodePatch(name = "Patch") { 
 | 
			
		||||
val patch = bytecodePatch(name = "Patch") {
 | 
			
		||||
    dependsOn(
 | 
			
		||||
        bytecodePatch(name = "Dependency") { 
 | 
			
		||||
        bytecodePatch(name = "Dependency") {
 | 
			
		||||
            execute {
 | 
			
		||||
                print("1")
 | 
			
		||||
            }
 | 
			
		||||
@@ -251,10 +249,10 @@ The same order is followed for multiple patches depending on the patch.
 | 
			
		||||
- A patch can declare compatibility with specific packages and versions,
 | 
			
		||||
  but patches can still be executed on any package or version.
 | 
			
		||||
  It is recommended that compatibility is specified to present known compatible packages and versions.
 | 
			
		||||
   - If `compatibleWith` is not used, the patch is treated as compatible with any package
 | 
			
		||||
    - If `compatibleWith` is not used, the patch is treated as compatible with any package
 | 
			
		||||
- If a package is specified with no versions, the patch is compatible with any version of the package
 | 
			
		||||
- If an empty array of versions is specified, the patch is not compatible with any version of the package.
 | 
			
		||||
    This is useful for declaring incompatibility with a specific package.
 | 
			
		||||
  This is useful for declaring incompatibility with a specific package.
 | 
			
		||||
- A patch can raise a `PatchException` at any time of execution to indicate that the patch failed to execute.
 | 
			
		||||
 | 
			
		||||
## ⏭️ What's next
 | 
			
		||||
 
 | 
			
		||||
@@ -96,21 +96,21 @@ Example of patches:
 | 
			
		||||
@Surpress("unused")
 | 
			
		||||
val bytecodePatch = bytecodePatch {
 | 
			
		||||
    execute { 
 | 
			
		||||
        // TODO
 | 
			
		||||
        // More about this on the next page of the documentation.
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Surpress("unused")
 | 
			
		||||
val rawResourcePatch = rawResourcePatch {
 | 
			
		||||
    execute { 
 | 
			
		||||
        // TODO
 | 
			
		||||
    execute {
 | 
			
		||||
        // More about this on the next page of the documentation.
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Surpress("unused")
 | 
			
		||||
val resourcePatch = resourcePatch {
 | 
			
		||||
    execute { 
 | 
			
		||||
        // TODO
 | 
			
		||||
    execute {
 | 
			
		||||
        // More about this on the next page of the documentation.
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										110
									
								
								docs/4_apis.md
									
									
									
									
									
								
							
							
						
						
									
										110
									
								
								docs/4_apis.md
									
									
									
									
									
								
							@@ -4,18 +4,107 @@ A handful of APIs are available to make patch development easier and more effici
 | 
			
		||||
 | 
			
		||||
## 📙 Overview
 | 
			
		||||
 | 
			
		||||
1. 👹 Mutate classes with `context.proxy(ClassDef)`
 | 
			
		||||
2. 🔍 Find and proxy existing classes with `classBy(Predicate)` and `classByType(String)`
 | 
			
		||||
3. 🏃 Easily access referenced methods recursively by index with `MethodNavigator`
 | 
			
		||||
4. 🔨 Make use of extension functions from `BytecodeUtils` and `ResourceUtils` with certain applications
 | 
			
		||||
(Available in ReVanced Patches)
 | 
			
		||||
5. 💾 Read and write (decoded) resources with `ResourcePatchContext.get(Path, Boolean)`
 | 
			
		||||
6. 📃 Read and write DOM files using `ResourcePatchContext.document`
 | 
			
		||||
1. 👹 Create mutable replacements of classes with `proxy(ClassDef)`
 | 
			
		||||
2. 🔍 Find and create mutable replaces with `classBy(Predicate)`
 | 
			
		||||
3. 🏃 Navigate method calls recursively by index with `navigate(Method)`
 | 
			
		||||
4. 💾 Read and write resource files with `get(String, Boolean)` and `delete(String)`
 | 
			
		||||
5. 📃 Read and write DOM files using `document(String)` and  `document(InputStream)`
 | 
			
		||||
 | 
			
		||||
### 🧰 APIs
 | 
			
		||||
 | 
			
		||||
> [!WARNING]
 | 
			
		||||
> This section is still under construction and may be incomplete.
 | 
			
		||||
#### 👹 `proxy(ClassDef)`
 | 
			
		||||
 | 
			
		||||
By default, the classes are immutable, meaning they cannot be modified.
 | 
			
		||||
To make a class mutable, use the `proxy(ClassDef)` function.
 | 
			
		||||
This function creates a lazy mutable copy of the class definition.
 | 
			
		||||
Accessing the property will replace the original class definition with the mutable copy,
 | 
			
		||||
thus allowing you to make changes to the class. Subsequent accesses will return the same mutable copy.
 | 
			
		||||
 | 
			
		||||
```kt
 | 
			
		||||
execute {
 | 
			
		||||
    val mutableClass = proxy(classDef)
 | 
			
		||||
    mutableClass.methods.add(Method())
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 🔍 `classBy(Predicate)`
 | 
			
		||||
 | 
			
		||||
The `classBy(Predicate)` function is an alternative to finding and creating mutable classes by a predicate.
 | 
			
		||||
It automatically proxies the class definition, making it mutable.
 | 
			
		||||
 | 
			
		||||
```kt
 | 
			
		||||
execute {
 | 
			
		||||
    // Alternative to proxy(classes.find { it.name == "Lcom/example/MyClass;" })?.classDef
 | 
			
		||||
    val classDef = classBy { it.name == "Lcom/example/MyClass;" }?.classDef
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 🏃 `navigate(Method).at(index)`
 | 
			
		||||
 | 
			
		||||
The `navigate(Method)` function allows you to navigate method calls recursively by index.
 | 
			
		||||
 | 
			
		||||
```kt
 | 
			
		||||
execute {
 | 
			
		||||
    // Sequentially navigate to the instructions at index 1 within 'someMethod'.
 | 
			
		||||
    val method = navigate(someMethod).to(1).original() // original() returns the original immutable method.
 | 
			
		||||
    
 | 
			
		||||
    // Further navigate to the second occurrence where the instruction's opcode is 'INVOKEVIRTUAL'.
 | 
			
		||||
    // stop() returns the mutable copy of the method.
 | 
			
		||||
    val method = navigate(someMethod).to(2) { instruction -> instruction.opcode == Opcode.INVOKEVIRTUAL }.stop()
 | 
			
		||||
    
 | 
			
		||||
    // Alternatively, to stop(), you can delegate the method to a variable.
 | 
			
		||||
    val method by navigate(someMethod).to(1)
 | 
			
		||||
    
 | 
			
		||||
    // You can chain multiple calls to at() to navigate deeper into the method.
 | 
			
		||||
    val method by navigate(someMethod).to(1).to(2, 3, 4).to(5)
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 💾 `get(String, Boolean)` and `delete(String)`
 | 
			
		||||
 | 
			
		||||
The `get(String, Boolean)` function returns a `File` object that can be used to read and write resource files.
 | 
			
		||||
 | 
			
		||||
```kt
 | 
			
		||||
execute {
 | 
			
		||||
    val file = get("res/values/strings.xml")
 | 
			
		||||
    val content = file.readText()
 | 
			
		||||
    file.writeText(content)
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
The `delete` function can mark files for deletion when the APK is rebuilt.
 | 
			
		||||
 | 
			
		||||
```kt
 | 
			
		||||
execute {
 | 
			
		||||
    delete("res/values/strings.xml")
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 📃 `document(String)`  and  `document(InputStream)`
 | 
			
		||||
 | 
			
		||||
The `document` function is used to read and write DOM files.
 | 
			
		||||
 | 
			
		||||
```kt
 | 
			
		||||
execute {
 | 
			
		||||
    document("res/values/strings.xml").use { document ->
 | 
			
		||||
        val element = doc.createElement("string").apply {
 | 
			
		||||
            textContent = "Hello, World!"
 | 
			
		||||
        }
 | 
			
		||||
        document.documentElement.appendChild(element)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
You can also read documents from an `InputStream`:
 | 
			
		||||
 | 
			
		||||
```kt
 | 
			
		||||
execute {
 | 
			
		||||
    val inputStream = classLoader.getResourceAsStream("some.xml")
 | 
			
		||||
    document(inputStream).use { document ->
 | 
			
		||||
        // ...
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## 🎉 Afterword
 | 
			
		||||
 | 
			
		||||
@@ -23,5 +112,6 @@ ReVanced Patcher is a powerful library to patch Android applications, offering a
 | 
			
		||||
that outlive app updates. Patches make up ReVanced; without you, the community of patch developers,
 | 
			
		||||
ReVanced would not be what it is today. We hope that this documentation has been helpful to you
 | 
			
		||||
and are excited to see what you will create with ReVanced Patcher. If you have any questions or need help,
 | 
			
		||||
talk to us on one of our platforms linked on [revanced.app](https://revanced.app) or open an issue in case of a bug or feature request,  
 | 
			
		||||
talk to us on one of our platforms linked on [revanced.app](https://revanced.app) or open an issue in case of a bug or
 | 
			
		||||
feature request,  
 | 
			
		||||
ReVanced
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,3 @@
 | 
			
		||||
org.gradle.parallel = true
 | 
			
		||||
org.gradle.caching = true
 | 
			
		||||
version = 20.0.1-dev.1
 | 
			
		||||
version = 21.1.0-dev.5
 | 
			
		||||
 
 | 
			
		||||
@@ -1,14 +1,14 @@
 | 
			
		||||
[versions]
 | 
			
		||||
android = "4.1.1.4"
 | 
			
		||||
apktool-lib = "2.9.3"
 | 
			
		||||
binary-compatibility-validator = "0.15.1"
 | 
			
		||||
kotlin = "2.0.0"
 | 
			
		||||
kotlinx-coroutines-core = "1.8.1"
 | 
			
		||||
mockk = "1.13.10"
 | 
			
		||||
apktool-lib = "2.10.1.1"
 | 
			
		||||
binary-compatibility-validator = "0.18.1"
 | 
			
		||||
kotlin = "2.2.21"
 | 
			
		||||
kotlinx-coroutines-core = "1.10.2"
 | 
			
		||||
mockk = "1.14.5"
 | 
			
		||||
multidexlib2 = "3.0.3.r3"
 | 
			
		||||
# Tracking https://github.com/google/smali/issues/64.
 | 
			
		||||
#noinspection GradleDependency
 | 
			
		||||
smali = "3.0.5"
 | 
			
		||||
smali = "3.0.9"
 | 
			
		||||
xpp3 = "1.1.4c"
 | 
			
		||||
 | 
			
		||||
[libraries]
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4047
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										4047
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -3,7 +3,7 @@
 | 
			
		||||
    "@saithodev/semantic-release-backmerge": "^4.0.1",
 | 
			
		||||
    "@semantic-release/changelog": "^6.0.3",
 | 
			
		||||
    "@semantic-release/git": "^10.0.1",
 | 
			
		||||
    "gradle-semantic-release-plugin": "^1.9.1",
 | 
			
		||||
    "semantic-release": "^23.0.2"
 | 
			
		||||
    "gradle-semantic-release-plugin": "^1.10.1",
 | 
			
		||||
    "semantic-release": "^24.2.9"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -91,20 +91,14 @@ class Patcher(private val config: PatcherConfig) : Closeable {
 | 
			
		||||
            }.also { executedPatches[this] = it }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Prevent from decoding the app manifest twice if it is not needed.
 | 
			
		||||
        // Prevent decoding the app manifest twice if it is not needed.
 | 
			
		||||
        if (config.resourceMode != ResourcePatchContext.ResourceMode.NONE) {
 | 
			
		||||
            context.resourceContext.decodeResources(config.resourceMode)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        logger.info("Merging extensions")
 | 
			
		||||
        logger.info("Initializing lookup maps")
 | 
			
		||||
 | 
			
		||||
        context.executablePatches.forEachRecursively { patch ->
 | 
			
		||||
            if (patch is BytecodePatch && patch.extension != null) {
 | 
			
		||||
                context.bytecodeContext.merge(patch.extension)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Initialize lookup maps.
 | 
			
		||||
        // Accessing the lazy lookup maps to initialize them.
 | 
			
		||||
        context.bytecodeContext.lookupMaps
 | 
			
		||||
 | 
			
		||||
        logger.info("Executing patches")
 | 
			
		||||
 
 | 
			
		||||
@@ -12,16 +12,32 @@ import java.util.logging.Logger
 | 
			
		||||
 * @param temporaryFilesPath A path to a folder to store temporary files in.
 | 
			
		||||
 * @param aaptBinaryPath A path to a custom aapt binary.
 | 
			
		||||
 * @param frameworkFileDirectory A path to the directory to cache the framework file in.
 | 
			
		||||
 * @param multithreadingDexFileWriter Whether to use multiple threads for writing dex files.
 | 
			
		||||
 * This has impact on memory usage and performance.
 | 
			
		||||
 */
 | 
			
		||||
class PatcherConfig(
 | 
			
		||||
    internal val apkFile: File,
 | 
			
		||||
    private val temporaryFilesPath: File = File("revanced-temporary-files"),
 | 
			
		||||
    aaptBinaryPath: String? = null,
 | 
			
		||||
    aaptBinaryPath: File? = null,
 | 
			
		||||
    frameworkFileDirectory: String? = null,
 | 
			
		||||
    internal val multithreadingDexFileWriter: Boolean = false,
 | 
			
		||||
) {
 | 
			
		||||
    /**
 | 
			
		||||
     * The configuration for the patcher.
 | 
			
		||||
     *
 | 
			
		||||
     * @param apkFile The apk file to patch.
 | 
			
		||||
     * @param temporaryFilesPath A path to a folder to store temporary files in.
 | 
			
		||||
     * @param aaptBinaryPath A path to a custom aapt binary.
 | 
			
		||||
     * @param frameworkFileDirectory A path to the directory to cache the framework file in.
 | 
			
		||||
     */
 | 
			
		||||
    @Deprecated(
 | 
			
		||||
        "Use the constructor with a File for aaptBinaryPath instead.",
 | 
			
		||||
        ReplaceWith("PatcherConfig(apkFile, temporaryFilesPath, aaptBinaryPath?.let { File(it) }, frameworkFileDirectory)"),
 | 
			
		||||
    )
 | 
			
		||||
    constructor(
 | 
			
		||||
        apkFile: File,
 | 
			
		||||
        temporaryFilesPath: File = File("revanced-temporary-files"),
 | 
			
		||||
        aaptBinaryPath: String? = null,
 | 
			
		||||
        frameworkFileDirectory: String? = null,
 | 
			
		||||
    ) : this(apkFile, temporaryFilesPath, aaptBinaryPath?.let { File(it) }, frameworkFileDirectory)
 | 
			
		||||
 | 
			
		||||
    private val logger = Logger.getLogger(PatcherConfig::class.java.name)
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -36,8 +52,7 @@ class PatcherConfig(
 | 
			
		||||
     */
 | 
			
		||||
    internal val resourceConfig =
 | 
			
		||||
        Config.getDefaultConfig().apply {
 | 
			
		||||
            useAapt2 = true
 | 
			
		||||
            aaptPath = aaptBinaryPath ?: ""
 | 
			
		||||
            aaptBinary = aaptBinaryPath
 | 
			
		||||
            frameworkDirectory = frameworkFileDirectory
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -29,12 +29,12 @@ class PatcherResult internal constructor(
 | 
			
		||||
     * @param resourcesApk The compiled resources.apk file.
 | 
			
		||||
     * @param otherResources The directory containing other resources files.
 | 
			
		||||
     * @param doNotCompress List of files that should not be compressed.
 | 
			
		||||
     * @param deleteResources List of predicates about resources that should be deleted.
 | 
			
		||||
     * @param deleteResources List of resources that should be deleted.
 | 
			
		||||
     */
 | 
			
		||||
    class PatchedResources internal constructor(
 | 
			
		||||
        val resourcesApk: File?,
 | 
			
		||||
        val otherResources: File?,
 | 
			
		||||
        val doNotCompress: Set<String>,
 | 
			
		||||
        val deleteResources: Set<(String) -> Boolean>,
 | 
			
		||||
        val deleteResources: Set<String>,
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,7 @@ import com.android.tools.smali.dexlib2.iface.ClassDef
 | 
			
		||||
import com.android.tools.smali.dexlib2.iface.DexFile
 | 
			
		||||
import com.android.tools.smali.dexlib2.iface.Method
 | 
			
		||||
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
 | 
			
		||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
 | 
			
		||||
import com.android.tools.smali.dexlib2.iface.reference.StringReference
 | 
			
		||||
import lanchon.multidexlib2.BasicDexFileNamer
 | 
			
		||||
import lanchon.multidexlib2.DexIO
 | 
			
		||||
@@ -21,7 +22,6 @@ import lanchon.multidexlib2.MultiDexIO
 | 
			
		||||
import lanchon.multidexlib2.RawDexIO
 | 
			
		||||
import java.io.Closeable
 | 
			
		||||
import java.io.FileFilter
 | 
			
		||||
import java.io.InputStream
 | 
			
		||||
import java.util.*
 | 
			
		||||
import java.util.logging.Logger
 | 
			
		||||
 | 
			
		||||
@@ -34,7 +34,7 @@ import java.util.logging.Logger
 | 
			
		||||
class BytecodePatchContext internal constructor(private val config: PatcherConfig) :
 | 
			
		||||
    PatchContext<Set<PatcherResult.PatchedDexFile>>,
 | 
			
		||||
    Closeable {
 | 
			
		||||
    private val logger = Logger.getLogger(BytecodePatchContext::class.java.name)
 | 
			
		||||
    private val logger = Logger.getLogger(this::class.java.name)
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * [Opcodes] of the supplied [PatcherConfig.apkFile].
 | 
			
		||||
@@ -60,52 +60,37 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
 | 
			
		||||
    internal val lookupMaps by lazy { LookupMaps(classes) }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * A map for lookup by [merge].
 | 
			
		||||
     */
 | 
			
		||||
    internal val classesByType = mutableMapOf<String, ClassDef>().apply {
 | 
			
		||||
        classes.forEach { classDef -> put(classDef.type, classDef) }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Merge an extension to [classes].
 | 
			
		||||
     * Merge the extension of [bytecodePatch] into the [BytecodePatchContext].
 | 
			
		||||
     * If no extension is present, the function will return early.
 | 
			
		||||
     *
 | 
			
		||||
     * @param extensionInputStream The input stream of the extension to merge.
 | 
			
		||||
     * @param bytecodePatch The [BytecodePatch] to merge the extension of.
 | 
			
		||||
     */
 | 
			
		||||
    internal fun merge(extensionInputStream: InputStream) {
 | 
			
		||||
        val extension = extensionInputStream.readAllBytes()
 | 
			
		||||
    internal fun mergeExtension(bytecodePatch: BytecodePatch) {
 | 
			
		||||
        bytecodePatch.extensionInputStream?.get()?.use { extensionStream ->
 | 
			
		||||
            RawDexIO.readRawDexFile(extensionStream, 0, null).classes.forEach { classDef ->
 | 
			
		||||
                val existingClass = lookupMaps.classesByType[classDef.type] ?: run {
 | 
			
		||||
                    logger.fine { "Adding class \"$classDef\"" }
 | 
			
		||||
 | 
			
		||||
        RawDexIO.readRawDexFile(extension, 0, null).classes.forEach { classDef ->
 | 
			
		||||
            val existingClass = classesByType[classDef.type] ?: run {
 | 
			
		||||
                logger.fine("Adding class \"$classDef\"")
 | 
			
		||||
                    classes += classDef
 | 
			
		||||
                    lookupMaps.classesByType[classDef.type] = classDef
 | 
			
		||||
 | 
			
		||||
                classes += classDef
 | 
			
		||||
                classesByType[classDef.type] = classDef
 | 
			
		||||
 | 
			
		||||
                return@forEach
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            logger.fine("Class \"$classDef\" exists already. Adding missing methods and fields.")
 | 
			
		||||
 | 
			
		||||
            existingClass.merge(classDef, this@BytecodePatchContext).let { mergedClass ->
 | 
			
		||||
                // If the class was merged, replace the original class with the merged class.
 | 
			
		||||
                if (mergedClass === existingClass) {
 | 
			
		||||
                    return@let
 | 
			
		||||
                    return@forEach
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                classes -= existingClass
 | 
			
		||||
                classes += mergedClass
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
                logger.fine { "Class \"$classDef\" exists already. Adding missing methods and fields." }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Find a class by its type using a contains check.
 | 
			
		||||
     *
 | 
			
		||||
     * @param type The type of the class.
 | 
			
		||||
     * @return A proxy for the first class that matches the type.
 | 
			
		||||
     */
 | 
			
		||||
    @Deprecated("Use classBy { type in it.type } instead.", ReplaceWith("classBy { type in it.type }"))
 | 
			
		||||
    fun classByType(type: String) = classBy { type in it.type }
 | 
			
		||||
                existingClass.merge(classDef, this@BytecodePatchContext).let { mergedClass ->
 | 
			
		||||
                    // If the class was merged, replace the original class with the merged class.
 | 
			
		||||
                    if (mergedClass === existingClass) {
 | 
			
		||||
                        return@let
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    classes -= existingClass
 | 
			
		||||
                    classes += mergedClass
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        } ?: logger.fine("No extension to merge")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Find a class with a predicate.
 | 
			
		||||
@@ -123,9 +108,9 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
 | 
			
		||||
     *
 | 
			
		||||
     * @return A proxy for the class.
 | 
			
		||||
     */
 | 
			
		||||
    fun proxy(classDef: ClassDef) = this@BytecodePatchContext.classes.proxyPool.find {
 | 
			
		||||
    fun proxy(classDef: ClassDef) = classes.proxyPool.find {
 | 
			
		||||
        it.immutableClass.type == classDef.type
 | 
			
		||||
    } ?: ClassProxy(classDef).also { this@BytecodePatchContext.classes.proxyPool.add(it) }
 | 
			
		||||
    } ?: ClassProxy(classDef).also { classes.proxyPool.add(it) }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Navigate a method.
 | 
			
		||||
@@ -134,7 +119,7 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
 | 
			
		||||
     *
 | 
			
		||||
     * @return A [MethodNavigator] for the method.
 | 
			
		||||
     */
 | 
			
		||||
    fun navigate(method: Method) = MethodNavigator(this@BytecodePatchContext, method)
 | 
			
		||||
    fun navigate(method: MethodReference) = MethodNavigator(method)
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Compile bytecode from the [BytecodePatchContext].
 | 
			
		||||
@@ -145,6 +130,9 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
 | 
			
		||||
    override fun get(): Set<PatcherResult.PatchedDexFile> {
 | 
			
		||||
        logger.info("Compiling patched dex files")
 | 
			
		||||
 | 
			
		||||
        // Free up memory before compiling the dex files.
 | 
			
		||||
        lookupMaps.close()
 | 
			
		||||
 | 
			
		||||
        val patchedDexFileResults =
 | 
			
		||||
            config.patchedFiles.resolve("dex").also {
 | 
			
		||||
                it.deleteRecursively() // Make sure the directory is empty.
 | 
			
		||||
@@ -152,7 +140,7 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
 | 
			
		||||
            }.apply {
 | 
			
		||||
                MultiDexIO.writeDexFile(
 | 
			
		||||
                    true,
 | 
			
		||||
                    if (config.multithreadingDexFileWriter) -1 else 1,
 | 
			
		||||
                    -1,
 | 
			
		||||
                    this,
 | 
			
		||||
                    BasicDexFileNamer(),
 | 
			
		||||
                    object : DexFile {
 | 
			
		||||
@@ -162,7 +150,7 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
 | 
			
		||||
                        override fun getOpcodes() = this@BytecodePatchContext.opcodes
 | 
			
		||||
                    },
 | 
			
		||||
                    DexIO.DEFAULT_MAX_DEX_POOL_SIZE,
 | 
			
		||||
                ) { _, entryName, _ -> logger.info("Compiled $entryName") }
 | 
			
		||||
                ) { _, entryName, _ -> logger.info { "Compiled $entryName" } }
 | 
			
		||||
            }.listFiles(FileFilter { it.isFile })!!.map {
 | 
			
		||||
                PatcherResult.PatchedDexFile(it.name, it.inputStream())
 | 
			
		||||
            }.toSet()
 | 
			
		||||
@@ -178,47 +166,21 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
 | 
			
		||||
     * @param classes The list of classes to create the lookup maps from.
 | 
			
		||||
     */
 | 
			
		||||
    internal class LookupMaps internal constructor(classes: List<ClassDef>) : Closeable {
 | 
			
		||||
        /**
 | 
			
		||||
         * Classes associated by their type.
 | 
			
		||||
         */
 | 
			
		||||
        internal val classesByType = classes.associateBy { it.type }.toMutableMap()
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * All methods and the class they are a member of.
 | 
			
		||||
         */
 | 
			
		||||
        internal val allMethods = MethodClassPairs()
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Methods associated by its access flags, return type and parameter.
 | 
			
		||||
         */
 | 
			
		||||
        internal val methodsBySignature = MethodClassPairsLookupMap()
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Methods associated by strings referenced in it.
 | 
			
		||||
         */
 | 
			
		||||
        internal val methodsByStrings = MethodClassPairsLookupMap()
 | 
			
		||||
 | 
			
		||||
        // Lookup map for fast checking if a class exists by its type.
 | 
			
		||||
        val classesByType = mutableMapOf<String, ClassDef>().apply {
 | 
			
		||||
            classes.forEach { classDef -> put(classDef.type, classDef) }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        init {
 | 
			
		||||
            classes.forEach { classDef ->
 | 
			
		||||
                classDef.methods.forEach { method ->
 | 
			
		||||
                    val methodClassPair: MethodClassPair = method to classDef
 | 
			
		||||
 | 
			
		||||
                    // For fingerprints with no access or return type specified.
 | 
			
		||||
                    allMethods += methodClassPair
 | 
			
		||||
 | 
			
		||||
                    val accessFlagsReturnKey = method.accessFlags.toString() + method.returnType.first()
 | 
			
		||||
 | 
			
		||||
                    // Add <access><returnType> as the key.
 | 
			
		||||
                    methodsBySignature[accessFlagsReturnKey] = methodClassPair
 | 
			
		||||
 | 
			
		||||
                    // Add <access><returnType>[parameters] as the key.
 | 
			
		||||
                    methodsBySignature[
 | 
			
		||||
                        buildString {
 | 
			
		||||
                            append(accessFlagsReturnKey)
 | 
			
		||||
                            appendParameters(method.parameterTypes)
 | 
			
		||||
                        },
 | 
			
		||||
                    ] = methodClassPair
 | 
			
		||||
 | 
			
		||||
                    // Add strings contained in the method as the key.
 | 
			
		||||
                    method.instructionsOrNull?.forEach instructions@{ instruction ->
 | 
			
		||||
                        if (instruction.opcode != Opcode.CONST_STRING && instruction.opcode != Opcode.CONST_STRING_JUMBO) {
 | 
			
		||||
@@ -236,38 +198,14 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        internal companion object {
 | 
			
		||||
            /**
 | 
			
		||||
             * Appends a string based on the parameter reference types of this method.
 | 
			
		||||
             */
 | 
			
		||||
            internal fun StringBuilder.appendParameters(parameters: Iterable<CharSequence>) {
 | 
			
		||||
                // Maximum parameters to use in the signature key.
 | 
			
		||||
                // Some apps have methods with an incredible number of parameters (over 100 parameters have been seen).
 | 
			
		||||
                // To keep the signature map from becoming needlessly bloated,
 | 
			
		||||
                // group together in the same map entry all methods with the same access/return and 5 or more parameters.
 | 
			
		||||
                // The value of 5 was chosen based on local performance testing and is not set in stone.
 | 
			
		||||
                val maxSignatureParameters = 5
 | 
			
		||||
                // Must append a unique value before the parameters to distinguish this key includes the parameters.
 | 
			
		||||
                // If this is not appended, then methods with no parameters
 | 
			
		||||
                // will collide with different keys that specify access/return but omit the parameters.
 | 
			
		||||
                append("p:")
 | 
			
		||||
                parameters.forEachIndexed { index, parameter ->
 | 
			
		||||
                    if (index >= maxSignatureParameters) return
 | 
			
		||||
                    append(parameter.first())
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        override fun close() {
 | 
			
		||||
            allMethods.clear()
 | 
			
		||||
            methodsBySignature.clear()
 | 
			
		||||
            methodsByStrings.clear()
 | 
			
		||||
            classesByType.clear()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun close() {
 | 
			
		||||
        lookupMaps.close()
 | 
			
		||||
        classesByType.clear()
 | 
			
		||||
        classes.clear()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -20,16 +20,51 @@ import kotlin.reflect.typeOf
 | 
			
		||||
 * @constructor Create a new [Option].
 | 
			
		||||
 */
 | 
			
		||||
@Suppress("MemberVisibilityCanBePrivate", "unused")
 | 
			
		||||
class Option<T> @PublishedApi internal constructor(
 | 
			
		||||
class Option<T>
 | 
			
		||||
@PublishedApi
 | 
			
		||||
@Deprecated("Use the constructor with the name instead of a key instead.")
 | 
			
		||||
internal constructor(
 | 
			
		||||
    @Deprecated("Use the name property instead.")
 | 
			
		||||
    val key: String,
 | 
			
		||||
    val default: T? = null,
 | 
			
		||||
    val values: Map<String, T?>? = null,
 | 
			
		||||
    @Deprecated("Use the name property instead.")
 | 
			
		||||
    val title: String? = null,
 | 
			
		||||
    val description: String? = null,
 | 
			
		||||
    val required: Boolean = false,
 | 
			
		||||
    val type: KType,
 | 
			
		||||
    val validator: Option<T>.(T?) -> Boolean = { true },
 | 
			
		||||
) {
 | 
			
		||||
    /**
 | 
			
		||||
     * The name.
 | 
			
		||||
     */
 | 
			
		||||
    val name = key
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * An option.
 | 
			
		||||
     *
 | 
			
		||||
     * @param T The value type of the option.
 | 
			
		||||
     * @param name The name.
 | 
			
		||||
     * @param default The default value.
 | 
			
		||||
     * @param values Eligible option values mapped to a human-readable name.
 | 
			
		||||
     * @param description A description.
 | 
			
		||||
     * @param required Whether the option is required.
 | 
			
		||||
     * @param type The type of the option value (to handle type erasure).
 | 
			
		||||
     * @param validator The function to validate the option value.
 | 
			
		||||
     *
 | 
			
		||||
     * @constructor Create a new [Option].
 | 
			
		||||
     */
 | 
			
		||||
    @PublishedApi
 | 
			
		||||
    internal constructor(
 | 
			
		||||
        name: String,
 | 
			
		||||
        default: T? = null,
 | 
			
		||||
        values: Map<String, T?>? = null,
 | 
			
		||||
        description: String? = null,
 | 
			
		||||
        required: Boolean = false,
 | 
			
		||||
        type: KType,
 | 
			
		||||
        validator: Option<T>.(T?) -> Boolean = { true },
 | 
			
		||||
    ) : this(name, default, values, name, description, required, type, validator)
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The value of the [Option].
 | 
			
		||||
     */
 | 
			
		||||
@@ -109,7 +144,7 @@ class Option<T> @PublishedApi internal constructor(
 | 
			
		||||
class Options internal constructor(
 | 
			
		||||
    private val options: Map<String, Option<*>>,
 | 
			
		||||
) : Map<String, Option<*>> by options {
 | 
			
		||||
    internal constructor(options: Set<Option<*>>) : this(options.associateBy { it.key })
 | 
			
		||||
    internal constructor(options: Set<Option<*>>) : this(options.associateBy { it.name })
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set an option's value.
 | 
			
		||||
@@ -231,7 +266,7 @@ fun intOption(
 | 
			
		||||
    title: String? = null,
 | 
			
		||||
    description: String? = null,
 | 
			
		||||
    required: Boolean = false,
 | 
			
		||||
    validator: Option<Int?>.(Int?) -> Boolean = { true },
 | 
			
		||||
    validator: Option<Int>.(Int?) -> Boolean = { true },
 | 
			
		||||
) = option(
 | 
			
		||||
    key,
 | 
			
		||||
    default,
 | 
			
		||||
@@ -264,7 +299,7 @@ fun PatchBuilder<*>.intOption(
 | 
			
		||||
    title: String? = null,
 | 
			
		||||
    description: String? = null,
 | 
			
		||||
    required: Boolean = false,
 | 
			
		||||
    validator: Option<Int?>.(Int?) -> Boolean = { true },
 | 
			
		||||
    validator: Option<Int>.(Int?) -> Boolean = { true },
 | 
			
		||||
) = option(
 | 
			
		||||
    key,
 | 
			
		||||
    default,
 | 
			
		||||
@@ -297,7 +332,7 @@ fun booleanOption(
 | 
			
		||||
    title: String? = null,
 | 
			
		||||
    description: String? = null,
 | 
			
		||||
    required: Boolean = false,
 | 
			
		||||
    validator: Option<Boolean?>.(Boolean?) -> Boolean = { true },
 | 
			
		||||
    validator: Option<Boolean>.(Boolean?) -> Boolean = { true },
 | 
			
		||||
) = option(
 | 
			
		||||
    key,
 | 
			
		||||
    default,
 | 
			
		||||
@@ -330,7 +365,7 @@ fun PatchBuilder<*>.booleanOption(
 | 
			
		||||
    title: String? = null,
 | 
			
		||||
    description: String? = null,
 | 
			
		||||
    required: Boolean = false,
 | 
			
		||||
    validator: Option<Boolean?>.(Boolean?) -> Boolean = { true },
 | 
			
		||||
    validator: Option<Boolean>.(Boolean?) -> Boolean = { true },
 | 
			
		||||
) = option(
 | 
			
		||||
    key,
 | 
			
		||||
    default,
 | 
			
		||||
@@ -363,7 +398,7 @@ fun floatOption(
 | 
			
		||||
    title: String? = null,
 | 
			
		||||
    description: String? = null,
 | 
			
		||||
    required: Boolean = false,
 | 
			
		||||
    validator: Option<Float?>.(Float?) -> Boolean = { true },
 | 
			
		||||
    validator: Option<Float>.(Float?) -> Boolean = { true },
 | 
			
		||||
) = option(
 | 
			
		||||
    key,
 | 
			
		||||
    default,
 | 
			
		||||
@@ -396,7 +431,7 @@ fun PatchBuilder<*>.floatOption(
 | 
			
		||||
    title: String? = null,
 | 
			
		||||
    description: String? = null,
 | 
			
		||||
    required: Boolean = false,
 | 
			
		||||
    validator: Option<Float?>.(Float?) -> Boolean = { true },
 | 
			
		||||
    validator: Option<Float>.(Float?) -> Boolean = { true },
 | 
			
		||||
) = option(
 | 
			
		||||
    key,
 | 
			
		||||
    default,
 | 
			
		||||
@@ -429,7 +464,7 @@ fun longOption(
 | 
			
		||||
    title: String? = null,
 | 
			
		||||
    description: String? = null,
 | 
			
		||||
    required: Boolean = false,
 | 
			
		||||
    validator: Option<Long?>.(Long?) -> Boolean = { true },
 | 
			
		||||
    validator: Option<Long>.(Long?) -> Boolean = { true },
 | 
			
		||||
) = option(
 | 
			
		||||
    key,
 | 
			
		||||
    default,
 | 
			
		||||
@@ -462,7 +497,7 @@ fun PatchBuilder<*>.longOption(
 | 
			
		||||
    title: String? = null,
 | 
			
		||||
    description: String? = null,
 | 
			
		||||
    required: Boolean = false,
 | 
			
		||||
    validator: Option<Long?>.(Long?) -> Boolean = { true },
 | 
			
		||||
    validator: Option<Long>.(Long?) -> Boolean = { true },
 | 
			
		||||
) = option(
 | 
			
		||||
    key,
 | 
			
		||||
    default,
 | 
			
		||||
@@ -856,14 +891,14 @@ sealed class OptionException(errorMessage: String) : Exception(errorMessage, nul
 | 
			
		||||
     *
 | 
			
		||||
     * @param value The value that failed validation.
 | 
			
		||||
     */
 | 
			
		||||
    class ValueValidationException(value: Any?, option: Option<*>) : OptionException("The option value \"$value\" failed validation for ${option.key}")
 | 
			
		||||
    class ValueValidationException(value: Any?, option: Option<*>) : OptionException("The option value \"$value\" failed validation for ${option.name}")
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * An exception thrown when a value is required but null was passed.
 | 
			
		||||
     *
 | 
			
		||||
     * @param option The [Option] that requires a value.
 | 
			
		||||
     */
 | 
			
		||||
    class ValueRequiredException(option: Option<*>) : OptionException("The option ${option.key} requires a value, but the value was null")
 | 
			
		||||
    class ValueRequiredException(option: Option<*>) : OptionException("The option ${option.name} requires a value, but the value was null")
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * An exception thrown when a [Option] is not found.
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -10,9 +10,9 @@ import brut.androlib.ApkDecoder
 | 
			
		||||
import brut.androlib.apk.UsesFramework
 | 
			
		||||
import brut.androlib.res.Framework
 | 
			
		||||
import brut.androlib.res.ResourcesDecoder
 | 
			
		||||
import brut.androlib.res.decoder.AndroidManifestPullStreamDecoder
 | 
			
		||||
import brut.androlib.res.decoder.AndroidManifestResourceParser
 | 
			
		||||
import brut.androlib.res.decoder.XmlPullStreamDecoder
 | 
			
		||||
import brut.androlib.res.xml.ResXmlPatcher
 | 
			
		||||
import brut.androlib.res.xml.ResXmlUtils
 | 
			
		||||
import brut.directory.ExtFile
 | 
			
		||||
import java.io.InputStream
 | 
			
		||||
import java.io.OutputStream
 | 
			
		||||
@@ -32,78 +32,81 @@ class ResourcePatchContext internal constructor(
 | 
			
		||||
    private val logger = Logger.getLogger(ResourcePatchContext::class.java.name)
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Read and write documents in the [PatcherConfig.apkFiles].
 | 
			
		||||
     * Read a document from an [InputStream].
 | 
			
		||||
     */
 | 
			
		||||
    val document = DocumentOperatable()
 | 
			
		||||
    fun document(inputStream: InputStream) = Document(inputStream)
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Predicate to delete resources from [PatcherConfig.apkFiles].
 | 
			
		||||
     * Read and write documents in the [PatcherConfig.apkFiles].
 | 
			
		||||
     */
 | 
			
		||||
    private val deleteResources = mutableSetOf<(String) -> Boolean>()
 | 
			
		||||
    fun document(path: String) = Document(get(path))
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set of resources from [PatcherConfig.apkFiles] to delete.
 | 
			
		||||
     */
 | 
			
		||||
    private val deleteResources = mutableSetOf<String>()
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Decode resources of [PatcherConfig.apkFile].
 | 
			
		||||
     *
 | 
			
		||||
     * @param mode The [ResourceMode] to use.
 | 
			
		||||
     */
 | 
			
		||||
    internal fun decodeResources(mode: ResourceMode) =
 | 
			
		||||
        with(packageMetadata.apkInfo) {
 | 
			
		||||
            config.initializeTemporaryFilesDirectories()
 | 
			
		||||
    internal fun decodeResources(mode: ResourceMode) = with(packageMetadata.apkInfo) {
 | 
			
		||||
        config.initializeTemporaryFilesDirectories()
 | 
			
		||||
 | 
			
		||||
            // Needed to decode resources.
 | 
			
		||||
            val resourcesDecoder = ResourcesDecoder(config.resourceConfig, this)
 | 
			
		||||
        // Needed to decode resources.
 | 
			
		||||
        val resourcesDecoder = ResourcesDecoder(config.resourceConfig, this)
 | 
			
		||||
 | 
			
		||||
            if (mode == ResourceMode.FULL) {
 | 
			
		||||
                logger.info("Decoding resources")
 | 
			
		||||
        if (mode == ResourceMode.FULL) {
 | 
			
		||||
            logger.info("Decoding resources")
 | 
			
		||||
 | 
			
		||||
                resourcesDecoder.decodeResources(config.apkFiles)
 | 
			
		||||
                resourcesDecoder.decodeManifest(config.apkFiles)
 | 
			
		||||
            resourcesDecoder.decodeResources(config.apkFiles)
 | 
			
		||||
            resourcesDecoder.decodeManifest(config.apkFiles)
 | 
			
		||||
 | 
			
		||||
                // Needed to record uncompressed files.
 | 
			
		||||
                val apkDecoder = ApkDecoder(config.resourceConfig, this)
 | 
			
		||||
                apkDecoder.recordUncompressedFiles(resourcesDecoder.resFileMapping)
 | 
			
		||||
            // Needed to record uncompressed files.
 | 
			
		||||
            ApkDecoder(this, config.resourceConfig).recordUncompressedFiles(resourcesDecoder.resFileMapping)
 | 
			
		||||
 | 
			
		||||
                usesFramework =
 | 
			
		||||
                    UsesFramework().apply {
 | 
			
		||||
                        ids = resourcesDecoder.resTable.listFramePackages().map { it.id }
 | 
			
		||||
                    }
 | 
			
		||||
            } else {
 | 
			
		||||
                logger.info("Decoding app manifest")
 | 
			
		||||
 | 
			
		||||
                // Decode manually instead of using resourceDecoder.decodeManifest
 | 
			
		||||
                // because it does not support decoding to an OutputStream.
 | 
			
		||||
                XmlPullStreamDecoder(
 | 
			
		||||
                    AndroidManifestResourceParser(resourcesDecoder.resTable),
 | 
			
		||||
                    resourcesDecoder.resXmlSerializer,
 | 
			
		||||
                ).decodeManifest(
 | 
			
		||||
                    apkFile.directory.getFileInput("AndroidManifest.xml"),
 | 
			
		||||
                    // Older Android versions do not support OutputStream.nullOutputStream()
 | 
			
		||||
                    object : OutputStream() {
 | 
			
		||||
                        override fun write(b: Int) { // Do nothing.
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
                // Get the package name and version from the manifest using the XmlPullStreamDecoder.
 | 
			
		||||
                // XmlPullStreamDecoder.decodeManifest() sets metadata.apkInfo.
 | 
			
		||||
                packageMetadata.let { metadata ->
 | 
			
		||||
                    metadata.packageName = resourcesDecoder.resTable.packageRenamed
 | 
			
		||||
                    versionInfo.let {
 | 
			
		||||
                        metadata.packageVersion = it.versionName ?: it.versionCode
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    /*
 | 
			
		||||
                     The ResTable if flagged as sparse if the main package is not loaded, which is the case here,
 | 
			
		||||
                     because ResourcesDecoder.decodeResources loads the main package
 | 
			
		||||
                     and not XmlPullStreamDecoder.decodeManifest.
 | 
			
		||||
                     See ARSCDecoder.readTableType for more info.
 | 
			
		||||
 | 
			
		||||
                     Set this to false again to prevent the ResTable from being flagged as sparse falsely.
 | 
			
		||||
                     */
 | 
			
		||||
                    metadata.apkInfo.sparseResources = false
 | 
			
		||||
            usesFramework =
 | 
			
		||||
                UsesFramework().apply {
 | 
			
		||||
                    ids = resourcesDecoder.resTable.listFramePackages().map { it.id }
 | 
			
		||||
                }
 | 
			
		||||
        } else {
 | 
			
		||||
            logger.info("Decoding app manifest")
 | 
			
		||||
 | 
			
		||||
            // Decode manually instead of using resourceDecoder.decodeManifest
 | 
			
		||||
            // because it does not support decoding to an OutputStream.
 | 
			
		||||
            AndroidManifestPullStreamDecoder(
 | 
			
		||||
                AndroidManifestResourceParser(resourcesDecoder.resTable),
 | 
			
		||||
                resourcesDecoder.newXmlSerializer(),
 | 
			
		||||
            ).decode(
 | 
			
		||||
                apkFile.directory.getFileInput("AndroidManifest.xml"),
 | 
			
		||||
                // Older Android versions do not support OutputStream.nullOutputStream()
 | 
			
		||||
                object : OutputStream() {
 | 
			
		||||
                    override fun write(b: Int) { // Do nothing.
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            // Get the package name and version from the manifest using the XmlPullStreamDecoder.
 | 
			
		||||
            // AndroidManifestPullStreamDecoder.decode() sets metadata.apkInfo.
 | 
			
		||||
            packageMetadata.let { metadata ->
 | 
			
		||||
                metadata.packageName = resourcesDecoder.resTable.packageRenamed
 | 
			
		||||
                versionInfo.let {
 | 
			
		||||
                    metadata.packageVersion = it.versionName ?: it.versionCode
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                /*
 | 
			
		||||
                 The ResTable if flagged as sparse if the main package is not loaded, which is the case here,
 | 
			
		||||
                 because ResourcesDecoder.decodeResources loads the main package
 | 
			
		||||
                 and not AndroidManifestPullStreamDecoder.decode.
 | 
			
		||||
                 See ARSCDecoder.readTableType for more info.
 | 
			
		||||
 | 
			
		||||
                 Set this to false again to prevent the ResTable from being flagged as sparse falsely.
 | 
			
		||||
                 */
 | 
			
		||||
                metadata.apkInfo.sparseResources = false
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Compile resources in [PatcherConfig.apkFiles].
 | 
			
		||||
@@ -125,10 +128,10 @@ class ResourcePatchContext internal constructor(
 | 
			
		||||
                    AaptInvoker(
 | 
			
		||||
                        config.resourceConfig,
 | 
			
		||||
                        packageMetadata.apkInfo,
 | 
			
		||||
                    ).invokeAapt(
 | 
			
		||||
                    ).invoke(
 | 
			
		||||
                        resources.resolve("resources.apk"),
 | 
			
		||||
                        config.apkFiles.resolve("AndroidManifest.xml").also {
 | 
			
		||||
                            ResXmlPatcher.fixingPublicAttrsInProviderAttributes(it)
 | 
			
		||||
                            ResXmlUtils.fixingPublicAttrsInProviderAttributes(it)
 | 
			
		||||
                        },
 | 
			
		||||
                        config.apkFiles.resolve("res"),
 | 
			
		||||
                        null,
 | 
			
		||||
@@ -201,11 +204,11 @@ class ResourcePatchContext internal constructor(
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Stage a file to be deleted from [PatcherConfig.apkFile].
 | 
			
		||||
     * Mark a file for deletion when the APK is rebuilt.
 | 
			
		||||
     *
 | 
			
		||||
     * @param shouldDelete The predicate to stage the file for deletion given its name.
 | 
			
		||||
     * @param name The name of the file to delete.
 | 
			
		||||
     */
 | 
			
		||||
    fun stageDelete(shouldDelete: (String) -> Boolean) = deleteResources.add(shouldDelete)
 | 
			
		||||
    fun delete(name: String) = deleteResources.add(name)
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * How to handle resources decoding and compiling.
 | 
			
		||||
@@ -227,10 +230,4 @@ class ResourcePatchContext internal constructor(
 | 
			
		||||
         */
 | 
			
		||||
        NONE,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    inner class DocumentOperatable {
 | 
			
		||||
        operator fun get(inputStream: InputStream) = Document(inputStream)
 | 
			
		||||
 | 
			
		||||
        operator fun get(path: String) = Document(this@ResourcePatchContext[path])
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -60,7 +60,7 @@ internal object ClassMerger {
 | 
			
		||||
 | 
			
		||||
        if (missingMethods.isEmpty()) return this
 | 
			
		||||
 | 
			
		||||
        logger.fine("Found ${missingMethods.size} missing methods")
 | 
			
		||||
        logger.fine { "Found ${missingMethods.size} missing methods" }
 | 
			
		||||
 | 
			
		||||
        return asMutableClass().apply {
 | 
			
		||||
            methods.addAll(missingMethods.map { it.toMutable() })
 | 
			
		||||
@@ -80,7 +80,7 @@ internal object ClassMerger {
 | 
			
		||||
 | 
			
		||||
        if (missingFields.isEmpty()) return this
 | 
			
		||||
 | 
			
		||||
        logger.fine("Found ${missingFields.size} missing fields")
 | 
			
		||||
        logger.fine { "Found ${missingFields.size} missing fields" }
 | 
			
		||||
 | 
			
		||||
        return asMutableClass().apply {
 | 
			
		||||
            fields.addAll(missingFields.map { it.toMutable() })
 | 
			
		||||
@@ -100,7 +100,7 @@ internal object ClassMerger {
 | 
			
		||||
            context.traverseClassHierarchy(this) {
 | 
			
		||||
                if (accessFlags.isPublic()) return@traverseClassHierarchy
 | 
			
		||||
 | 
			
		||||
                logger.fine("Publicizing ${this.type}")
 | 
			
		||||
                logger.fine { "Publicizing ${this.type}" }
 | 
			
		||||
 | 
			
		||||
                accessFlags = accessFlags.toPublic()
 | 
			
		||||
            }
 | 
			
		||||
@@ -124,7 +124,7 @@ internal object ClassMerger {
 | 
			
		||||
 | 
			
		||||
        if (brokenFields.isEmpty()) return this
 | 
			
		||||
 | 
			
		||||
        logger.fine("Found ${brokenFields.size} broken fields")
 | 
			
		||||
        logger.fine { "Found ${brokenFields.size} broken fields" }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Make a field public.
 | 
			
		||||
@@ -153,7 +153,7 @@ internal object ClassMerger {
 | 
			
		||||
 | 
			
		||||
        if (brokenMethods.isEmpty()) return this
 | 
			
		||||
 | 
			
		||||
        logger.fine("Found ${brokenMethods.size} methods")
 | 
			
		||||
        logger.fine { "Found ${brokenMethods.size} methods" }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Make a method public.
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@ import java.io.Closeable
 | 
			
		||||
import java.io.File
 | 
			
		||||
import java.io.InputStream
 | 
			
		||||
import javax.xml.parsers.DocumentBuilderFactory
 | 
			
		||||
import javax.xml.transform.OutputKeys
 | 
			
		||||
import javax.xml.transform.TransformerFactory
 | 
			
		||||
import javax.xml.transform.dom.DOMSource
 | 
			
		||||
import javax.xml.transform.stream.StreamResult
 | 
			
		||||
@@ -34,15 +35,22 @@ class Document internal constructor(
 | 
			
		||||
                readerCount.remove(it)
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            it.outputStream().use { stream ->
 | 
			
		||||
                TransformerFactory.newInstance()
 | 
			
		||||
                    .newTransformer()
 | 
			
		||||
                    .transform(DOMSource(this), StreamResult(stream))
 | 
			
		||||
            val transformer = TransformerFactory.newInstance().newTransformer()
 | 
			
		||||
            // Set to UTF-16 to prevent surrogate pairs from being escaped to invalid numeric character references, but save as UTF-8.
 | 
			
		||||
            if (isAndroid) {
 | 
			
		||||
                transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-16")
 | 
			
		||||
                transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes")
 | 
			
		||||
                it.bufferedWriter(charset = Charsets.UTF_8).use { writer ->
 | 
			
		||||
                    transformer.transform(DOMSource(this), StreamResult(writer))
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                transformer.transform(DOMSource(this), StreamResult(it))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private companion object {
 | 
			
		||||
        private val readerCount = mutableMapOf<File, Int>()
 | 
			
		||||
        private val isAndroid = System.getProperty("java.runtime.name") == "Android Runtime"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -12,11 +12,11 @@ import com.android.tools.smali.dexlib2.iface.instruction.Instruction
 | 
			
		||||
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
 | 
			
		||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
 | 
			
		||||
import com.android.tools.smali.dexlib2.util.MethodUtil
 | 
			
		||||
import kotlin.reflect.KProperty
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A navigator for methods.
 | 
			
		||||
 *
 | 
			
		||||
 * @param context The [BytecodePatchContext] to use.
 | 
			
		||||
 * @param startMethod The [Method] to start navigating from.
 | 
			
		||||
 *
 | 
			
		||||
 * @constructor Creates a new [MethodNavigator].
 | 
			
		||||
@@ -24,12 +24,16 @@ import com.android.tools.smali.dexlib2.util.MethodUtil
 | 
			
		||||
 * @throws NavigateException If the method does not have an implementation.
 | 
			
		||||
 * @throws NavigateException If the instruction at the specified index is not a method reference.
 | 
			
		||||
 */
 | 
			
		||||
class MethodNavigator internal constructor(private val context: BytecodePatchContext, private var startMethod: MethodReference) {
 | 
			
		||||
context(BytecodePatchContext)
 | 
			
		||||
class MethodNavigator internal constructor(
 | 
			
		||||
    private var startMethod: MethodReference,
 | 
			
		||||
) {
 | 
			
		||||
    private var lastNavigatedMethodReference = startMethod
 | 
			
		||||
 | 
			
		||||
    private val lastNavigatedMethodInstructions get() = with(immutable()) {
 | 
			
		||||
        instructionsOrNull ?: throw NavigateException("Method $definingClass.$name does not have an implementation.")
 | 
			
		||||
    }
 | 
			
		||||
    private val lastNavigatedMethodInstructions
 | 
			
		||||
        get() = with(original()) {
 | 
			
		||||
            instructionsOrNull ?: throw NavigateException("Method $this does not have an implementation.")
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Navigate to the method at the specified index.
 | 
			
		||||
@@ -38,7 +42,7 @@ class MethodNavigator internal constructor(private val context: BytecodePatchCon
 | 
			
		||||
     *
 | 
			
		||||
     * @return This [MethodNavigator].
 | 
			
		||||
     */
 | 
			
		||||
    fun at(vararg index: Int): MethodNavigator {
 | 
			
		||||
    fun to(vararg index: Int): MethodNavigator {
 | 
			
		||||
        index.forEach {
 | 
			
		||||
            lastNavigatedMethodReference = lastNavigatedMethodInstructions.getMethodReferenceAt(it)
 | 
			
		||||
        }
 | 
			
		||||
@@ -52,7 +56,7 @@ class MethodNavigator internal constructor(private val context: BytecodePatchCon
 | 
			
		||||
     * @param index The index of the method to navigate to.
 | 
			
		||||
     * @param predicate The predicate to match.
 | 
			
		||||
     */
 | 
			
		||||
    fun at(index: Int = 0, predicate: (Instruction) -> Boolean): MethodNavigator {
 | 
			
		||||
    fun to(index: Int = 0, predicate: (Instruction) -> Boolean): MethodNavigator {
 | 
			
		||||
        lastNavigatedMethodReference = lastNavigatedMethodInstructions.asSequence()
 | 
			
		||||
            .filter(predicate).asIterable().getMethodReferenceAt(index)
 | 
			
		||||
 | 
			
		||||
@@ -76,15 +80,22 @@ class MethodNavigator internal constructor(private val context: BytecodePatchCon
 | 
			
		||||
     *
 | 
			
		||||
     * @return The last navigated method mutably.
 | 
			
		||||
     */
 | 
			
		||||
    fun mutable() = context.classBy(matchesCurrentMethodReferenceDefiningClass)!!.mutableClass.firstMethodBySignature
 | 
			
		||||
    fun stop() = classBy(matchesCurrentMethodReferenceDefiningClass)!!.mutableClass.firstMethodBySignature
 | 
			
		||||
        as MutableMethod
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the last navigated method mutably.
 | 
			
		||||
     *
 | 
			
		||||
     * @return The last navigated method mutably.
 | 
			
		||||
     */
 | 
			
		||||
    operator fun getValue(nothing: Nothing?, property: KProperty<*>) = stop()
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the last navigated method immutably.
 | 
			
		||||
     *
 | 
			
		||||
     * @return The last navigated method immutably.
 | 
			
		||||
     */
 | 
			
		||||
    fun immutable() = context.classes.first(matchesCurrentMethodReferenceDefiningClass).firstMethodBySignature
 | 
			
		||||
    fun original(): Method = classes.first(matchesCurrentMethodReferenceDefiningClass).firstMethodBySignature
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Predicate to match the class defining the current method reference.
 | 
			
		||||
@@ -96,9 +107,10 @@ class MethodNavigator internal constructor(private val context: BytecodePatchCon
 | 
			
		||||
    /**
 | 
			
		||||
     * Find the first [lastNavigatedMethodReference] in the class.
 | 
			
		||||
     */
 | 
			
		||||
    private val ClassDef.firstMethodBySignature get() = methods.first {
 | 
			
		||||
        MethodUtil.methodSignaturesMatch(it, lastNavigatedMethodReference)
 | 
			
		||||
    }
 | 
			
		||||
    private val ClassDef.firstMethodBySignature
 | 
			
		||||
        get() = methods.first {
 | 
			
		||||
            MethodUtil.methodSignaturesMatch(it, lastNavigatedMethodReference)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * An exception thrown when navigating fails.
 | 
			
		||||
 
 | 
			
		||||
@@ -50,7 +50,7 @@ class InlineSmaliCompiler {
 | 
			
		||||
                    registers,
 | 
			
		||||
                    instructions,
 | 
			
		||||
                )
 | 
			
		||||
            val reader = InputStreamReader(input.byteInputStream())
 | 
			
		||||
            val reader = InputStreamReader(input.byteInputStream(), Charsets.UTF_8)
 | 
			
		||||
            val lexer: LexerErrorInterface = smaliFlexLexer(reader, 15)
 | 
			
		||||
            val tokens = CommonTokenStream(lexer as TokenSource)
 | 
			
		||||
            val parser = smaliParser(tokens)
 | 
			
		||||
 
 | 
			
		||||
@@ -5,16 +5,15 @@ import app.revanced.patcher.patch.BytecodePatchContext.LookupMaps
 | 
			
		||||
import app.revanced.patcher.util.ProxyClassList
 | 
			
		||||
import com.android.tools.smali.dexlib2.immutable.ImmutableClassDef
 | 
			
		||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
 | 
			
		||||
import io.mockk.every
 | 
			
		||||
import io.mockk.mockk
 | 
			
		||||
import io.mockk.*
 | 
			
		||||
import kotlinx.coroutines.flow.toList
 | 
			
		||||
import kotlinx.coroutines.runBlocking
 | 
			
		||||
import org.junit.jupiter.api.BeforeEach
 | 
			
		||||
import org.junit.jupiter.api.assertDoesNotThrow
 | 
			
		||||
import org.junit.jupiter.api.assertAll
 | 
			
		||||
import java.util.logging.Logger
 | 
			
		||||
import kotlin.test.Test
 | 
			
		||||
import kotlin.test.assertEquals
 | 
			
		||||
import kotlin.test.assertNull
 | 
			
		||||
import kotlin.test.assertNotNull
 | 
			
		||||
import kotlin.test.assertTrue
 | 
			
		||||
 | 
			
		||||
internal object PatcherTest {
 | 
			
		||||
@@ -149,19 +148,15 @@ internal object PatcherTest {
 | 
			
		||||
    @Test
 | 
			
		||||
    fun `throws if unmatched fingerprint match is delegated`() {
 | 
			
		||||
        val patch = bytecodePatch {
 | 
			
		||||
            // Fingerprint can never match.
 | 
			
		||||
            val match by fingerprint { }
 | 
			
		||||
            // Manually add the fingerprint.
 | 
			
		||||
            app.revanced.patcher.fingerprint { }()
 | 
			
		||||
 | 
			
		||||
            execute {
 | 
			
		||||
                // Fingerprint can never match.
 | 
			
		||||
                val fingerprint = fingerprint { }
 | 
			
		||||
 | 
			
		||||
                // Throws, because the fingerprint can't be matched.
 | 
			
		||||
                match.patternMatch
 | 
			
		||||
                fingerprint.patternMatch
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        assertEquals(2, patch.fingerprints.size)
 | 
			
		||||
 | 
			
		||||
        assertTrue(
 | 
			
		||||
            patch().exception != null,
 | 
			
		||||
            "Expected an exception because the fingerprint can't match.",
 | 
			
		||||
@@ -170,43 +165,6 @@ internal object PatcherTest {
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun `matches fingerprint`() {
 | 
			
		||||
        mockClassWithMethod()
 | 
			
		||||
 | 
			
		||||
        val patches = setOf(bytecodePatch { fingerprint { this returns "V" } })
 | 
			
		||||
 | 
			
		||||
        assertNull(
 | 
			
		||||
            patches.first().fingerprints.first().match,
 | 
			
		||||
            "Expected fingerprint to be matched before execution.",
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        patches()
 | 
			
		||||
 | 
			
		||||
        assertDoesNotThrow("Expected fingerprint to be matched.") {
 | 
			
		||||
            assertEquals(
 | 
			
		||||
                "V",
 | 
			
		||||
                patches.first().fingerprints.first().match!!.method.returnType,
 | 
			
		||||
                "Expected fingerprint to be matched.",
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private operator fun Set<Patch<*>>.invoke(): List<PatchResult> {
 | 
			
		||||
        every { patcher.context.executablePatches } returns toMutableSet()
 | 
			
		||||
        every { patcher.context.bytecodeContext.lookupMaps } returns LookupMaps(patcher.context.bytecodeContext.classes)
 | 
			
		||||
 | 
			
		||||
        return runBlocking { patcher().toList() }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private operator fun Patch<*>.invoke() = setOf(this)().first()
 | 
			
		||||
 | 
			
		||||
    private fun Any.setPrivateField(field: String, value: Any) {
 | 
			
		||||
        this::class.java.getDeclaredField(field).apply {
 | 
			
		||||
            this.isAccessible = true
 | 
			
		||||
            set(this@setPrivateField, value)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun mockClassWithMethod() {
 | 
			
		||||
        every { patcher.context.bytecodeContext.classes } returns ProxyClassList(
 | 
			
		||||
            mutableListOf(
 | 
			
		||||
                ImmutableClassDef(
 | 
			
		||||
@@ -232,6 +190,47 @@ internal object PatcherTest {
 | 
			
		||||
                ),
 | 
			
		||||
            ),
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        val fingerprint = fingerprint { returns("V") }
 | 
			
		||||
        val fingerprint2 = fingerprint { returns("V") }
 | 
			
		||||
        val fingerprint3 = fingerprint { returns("V") }
 | 
			
		||||
 | 
			
		||||
        val patches = setOf(
 | 
			
		||||
            bytecodePatch {
 | 
			
		||||
                execute {
 | 
			
		||||
                    fingerprint.match(classes.first().methods.first())
 | 
			
		||||
                    fingerprint2.match(classes.first())
 | 
			
		||||
                    fingerprint3.originalClassDef
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        patches()
 | 
			
		||||
 | 
			
		||||
        with(patcher.context.bytecodeContext) {
 | 
			
		||||
            assertAll(
 | 
			
		||||
                "Expected fingerprints to match.",
 | 
			
		||||
                { assertNotNull(fingerprint.originalClassDefOrNull) },
 | 
			
		||||
                { assertNotNull(fingerprint2.originalClassDefOrNull) },
 | 
			
		||||
                { assertNotNull(fingerprint3.originalClassDefOrNull) },
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private operator fun Set<Patch<*>>.invoke(): List<PatchResult> {
 | 
			
		||||
        every { patcher.context.executablePatches } returns toMutableSet()
 | 
			
		||||
        every { patcher.context.bytecodeContext.lookupMaps } returns LookupMaps(patcher.context.bytecodeContext.classes)
 | 
			
		||||
        every { with(patcher.context.bytecodeContext) { mergeExtension(any<BytecodePatch>()) } } just runs
 | 
			
		||||
 | 
			
		||||
        return runBlocking { patcher().toList() }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private operator fun Patch<*>.invoke() = setOf(this)().first()
 | 
			
		||||
 | 
			
		||||
    private fun Any.setPrivateField(field: String, value: Any) {
 | 
			
		||||
        this::class.java.getDeclaredField(field).apply {
 | 
			
		||||
            this.isAccessible = true
 | 
			
		||||
            set(this@setPrivateField, value)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,5 @@
 | 
			
		||||
package app.revanced.patcher.patch
 | 
			
		||||
 | 
			
		||||
import app.revanced.patcher.fingerprint
 | 
			
		||||
import kotlin.test.Test
 | 
			
		||||
import kotlin.test.assertEquals
 | 
			
		||||
 | 
			
		||||
@@ -24,23 +23,6 @@ internal object PatchTest {
 | 
			
		||||
        assertEquals("compatible.package", patch.compatiblePackages!!.first().first)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun `can create patch with fingerprints`() {
 | 
			
		||||
        val externalFingerprint = fingerprint {}
 | 
			
		||||
 | 
			
		||||
        val patch = bytecodePatch(name = "Test") {
 | 
			
		||||
            val externalFingerprintMatch by externalFingerprint()
 | 
			
		||||
            val internalFingerprintMatch by fingerprint {}
 | 
			
		||||
 | 
			
		||||
            execute {
 | 
			
		||||
                externalFingerprintMatch.method
 | 
			
		||||
                internalFingerprintMatch.method
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        assertEquals(2, patch.fingerprints.size)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun `can create patch with dependencies`() {
 | 
			
		||||
        val patch = bytecodePatch(name = "Test") {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user