diff --git a/build.gradle.kts b/build.gradle.kts index 23bfa04..1797fec 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -12,12 +12,12 @@ repositories { dependencies { implementation(kotlin("stdlib")) - testImplementation(kotlin("test")) - implementation("org.ow2.asm:asm:9.2") implementation("org.ow2.asm:asm-util:9.2") implementation("org.ow2.asm:asm-tree:9.2") implementation("org.ow2.asm:asm-commons:9.2") + implementation("com.google.code.gson:gson:2.9.0") + testImplementation(kotlin("test")) } tasks.test { diff --git a/settings.gradle.kts b/settings.gradle.kts index f4a0915..c675417 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,2 +1,2 @@ -rootProject.name = "patcher" +rootProject.name = "ReVanced Patcher" diff --git a/src/main/kotlin/net/revanced/patcher/MethodResolver.kt b/src/main/kotlin/net/revanced/patcher/MethodResolver.kt new file mode 100644 index 0000000..0e1a367 --- /dev/null +++ b/src/main/kotlin/net/revanced/patcher/MethodResolver.kt @@ -0,0 +1,38 @@ +package net.revanced.patcher + +import net.revanced.patcher.signature.model.Signature +import org.objectweb.asm.tree.InsnList +import org.objectweb.asm.tree.MethodNode + +internal class MethodResolver(private val targetMethods: List, private val signatures: List) { + fun resolve(): MutableMap { + val methods = mutableMapOf() + + for (signature in signatures) { + val method = targetMethods.firstOrNull { method -> + method.access == signature.accessors && + signature.parameters.all { parameter -> + method.parameters.any { methodParameter -> + true //TODO check for parameter element type + } + } && method.instructions.scanFor(signature.opcodes) + } ?: continue + methods[signature.name] = method + } + + return methods + } +} + +//TODO: implement returning the index of the needle in the hay +private fun InsnList.scanFor(pattern: Array): Boolean { + for (i in 0 until this.size()) { + var occurrence = 0 + while (i + occurrence < this.size()) { + if (this.get(i + occurrence).opcode != pattern.get(occurrence)) break + if (++occurrence >= pattern.size) return true + } + } + + return false +} diff --git a/src/main/kotlin/net/revanced/patcher/Patcher.kt b/src/main/kotlin/net/revanced/patcher/Patcher.kt index 56081ce..050095c 100644 --- a/src/main/kotlin/net/revanced/patcher/Patcher.kt +++ b/src/main/kotlin/net/revanced/patcher/Patcher.kt @@ -1,48 +1,55 @@ package net.revanced.patcher +import net.revanced.patcher.cache.Cache import net.revanced.patcher.patch.Patch -import net.revanced.patcher.signature.Signature -import net.revanced.patcher.store.ASMStore -import net.revanced.patcher.store.PatchStore -import net.revanced.patcher.util.Jar2ASM -import java.io.InputStream -import java.lang.IllegalStateException +import net.revanced.patcher.signature.model.Signature +import org.objectweb.asm.ClassReader +import org.objectweb.asm.tree.ClassNode +import org.objectweb.asm.tree.MethodNode +import java.io.File +import java.util.jar.JarFile -/** - * The patcher. (docs WIP) - * - * @param input the input stream to read from, must be a JAR file (for now) - */ -class Patcher( - input: InputStream, - private val signatures: Array, - patches: Array, +class Patcher private constructor( + file: File, + signatures: List ) { - private val patchStore = PatchStore() - private val asmStore = ASMStore() - - private val scanned = false + val cache = Cache() + private val patches: MutableList = mutableListOf() init { - patchStore.addPatches(*patches) - loadJar(input) + // collecting all methods here + val targetMethods: MutableList = mutableListOf() + + val jarFile = JarFile(file) + jarFile.stream().forEach { jarEntry -> + jarFile.getInputStream(jarEntry).use { jis -> + if (jarEntry.name.endsWith(".class")) { + val classNode = ClassNode() + ClassReader(jis.readAllBytes()).accept(classNode, ClassReader.EXPAND_FRAMES) + targetMethods.addAll(classNode.methods) + } + } + } + + // reducing to required methods via signatures + cache.Methods = MethodResolver(targetMethods, signatures).resolve() + } - fun scan() { - val methods = PatternScanner(signatures).resolve() + companion object { + fun loadFromFile(file: String, signatures: List): Patcher = Patcher(File(file), signatures) } - fun patch(): String? { - if (!scanned) throw IllegalStateException("Pattern scanner not yet ran") - for ((_, patch) in patchStore.patches) { + fun addPatches(vararg patches: Patch) { + this.patches.addAll(patches) + } + + fun executePatches(): String? { + for (patch in patches) { val result = patch.execute() if (result.isSuccess()) continue return result.error()!!.errorMessage() } return null } - - private fun loadJar(input: InputStream) { - asmStore.classes.putAll(Jar2ASM.jar2asm(input)) - } } \ No newline at end of file diff --git a/src/main/kotlin/net/revanced/patcher/PatternScanner.kt b/src/main/kotlin/net/revanced/patcher/PatternScanner.kt deleted file mode 100644 index a58b4b9..0000000 --- a/src/main/kotlin/net/revanced/patcher/PatternScanner.kt +++ /dev/null @@ -1,9 +0,0 @@ -package net.revanced.patcher - -import net.revanced.patcher.signature.Signature - -class PatternScanner(signatures: Array) { - fun resolve() { - TODO("Not yet implemented") - } -} diff --git a/src/main/kotlin/net/revanced/patcher/cache/Cache.kt b/src/main/kotlin/net/revanced/patcher/cache/Cache.kt new file mode 100644 index 0000000..d8fec48 --- /dev/null +++ b/src/main/kotlin/net/revanced/patcher/cache/Cache.kt @@ -0,0 +1,7 @@ +package net.revanced.patcher.cache + +import org.objectweb.asm.tree.MethodNode + +data class Cache( + var Methods: Map = mutableMapOf() +) diff --git a/src/main/kotlin/net/revanced/patcher/patch/Patch.kt b/src/main/kotlin/net/revanced/patcher/patch/Patch.kt index d5ae128..0a53faf 100644 --- a/src/main/kotlin/net/revanced/patcher/patch/Patch.kt +++ b/src/main/kotlin/net/revanced/patcher/patch/Patch.kt @@ -1,6 +1,6 @@ package net.revanced.patcher.patch -class Patch(val name: String, val fn: () -> PatchResult) { +class Patch(val patchName: String, val fn: () -> PatchResult) { fun execute(): PatchResult { return fn() } diff --git a/src/main/kotlin/net/revanced/patcher/signature/Signature.kt b/src/main/kotlin/net/revanced/patcher/signature/Signature.kt deleted file mode 100644 index 864e7d0..0000000 --- a/src/main/kotlin/net/revanced/patcher/signature/Signature.kt +++ /dev/null @@ -1,48 +0,0 @@ -package net.revanced.patcher.signature - -import org.objectweb.asm.Type - -/** - * An ASM signature list for the Patcher. - * - * @param name The name of the method. - * Do not use the actual method name, instead try to guess what the method name originally was. - * If you are unable to guess a method name, doing something like "patch-name-1" is fine too. - * For example: "override-codec-1". - * This method name will be used to find the corresponding patch. - * @param returns The return type/signature of the method. - * @param accessors The accessors of the method. - * @param parameters The parameter types/signatures of the method. - * @param opcodes The opcode pattern of the method, used to find the method by signature scanning. - */ -data class Signature( - val name: String, - val returns: Type, - val accessors: Array, - val parameters: Array, - val opcodes: Array -) { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as Signature - - if (name != other.name) return false - if (returns != other.returns) return false - if (!accessors.contentEquals(other.accessors)) return false - if (!parameters.contentEquals(other.parameters)) return false - if (!opcodes.contentEquals(other.opcodes)) return false - - return true - } - - override fun hashCode(): Int { - var result = name.hashCode() - result = 31 * result + returns.hashCode() - result = 31 * result + accessors.contentHashCode() - result = 31 * result + parameters.contentHashCode() - result = 31 * result + opcodes.contentHashCode() - return result - } -} diff --git a/src/main/kotlin/net/revanced/patcher/signature/SignatureLoader.kt b/src/main/kotlin/net/revanced/patcher/signature/SignatureLoader.kt new file mode 100644 index 0000000..2e9eac2 --- /dev/null +++ b/src/main/kotlin/net/revanced/patcher/signature/SignatureLoader.kt @@ -0,0 +1,12 @@ +package net.revanced.patcher.signature + +import com.google.gson.Gson +import net.revanced.patcher.signature.model.Signature + +object SignatureLoader { + private val gson = Gson() + + fun LoadFromJson(json: String): Array { + return gson.fromJson(json, Array::class.java) + } +} diff --git a/src/main/kotlin/net/revanced/patcher/signature/model/Signature.kt b/src/main/kotlin/net/revanced/patcher/signature/model/Signature.kt new file mode 100644 index 0000000..5c90ec2 --- /dev/null +++ b/src/main/kotlin/net/revanced/patcher/signature/model/Signature.kt @@ -0,0 +1,25 @@ +package net.revanced.patcher.signature.model + +import org.objectweb.asm.Type +import org.objectweb.asm.tree.ParameterNode + +/** + * An ASM signature list for the Patcher. + * + * @param name The name of the method. + * Do not use the actual method name, instead try to guess what the method name originally was. + * If you are unable to guess a method name, doing something like "patch-name-1" is fine too. + * For example: "override-codec-1". + * This method name will be used to find the corresponding patch. + * @param returns The return type/signature of the method. + * @param accessors The accessors of the method. + * @param parameters The parameter types/signatures of the method. + * @param opcodes The opcode pattern of the method, used to find the method by signature scanning. + */ +data class Signature( + val name: String, + val returns: Type, + @Suppress("ArrayInDataClass") val accessors: Int, + @Suppress("ArrayInDataClass") val parameters: Array, + @Suppress("ArrayInDataClass") val opcodes: Array +) \ No newline at end of file diff --git a/src/main/kotlin/net/revanced/patcher/store/ASMStore.kt b/src/main/kotlin/net/revanced/patcher/store/ASMStore.kt deleted file mode 100644 index c54e769..0000000 --- a/src/main/kotlin/net/revanced/patcher/store/ASMStore.kt +++ /dev/null @@ -1,9 +0,0 @@ -package net.revanced.patcher.store - -import org.objectweb.asm.tree.ClassNode -import org.objectweb.asm.tree.MethodNode - -class ASMStore { - val classes: MutableMap = mutableMapOf() - val methods: MutableMap = mutableMapOf() -} \ No newline at end of file diff --git a/src/main/kotlin/net/revanced/patcher/store/PatchStore.kt b/src/main/kotlin/net/revanced/patcher/store/PatchStore.kt deleted file mode 100644 index 2e1b101..0000000 --- a/src/main/kotlin/net/revanced/patcher/store/PatchStore.kt +++ /dev/null @@ -1,13 +0,0 @@ -package net.revanced.patcher.store - -import net.revanced.patcher.patch.Patch - -class PatchStore { - val patches: MutableMap = mutableMapOf() - - fun addPatches(vararg patches: Patch) { - for (patch in patches) { - this.patches[patch.name] = patch - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/net/revanced/patcher/util/Jar2ASM.kt b/src/main/kotlin/net/revanced/patcher/util/Jar2ASM.kt deleted file mode 100644 index c39b76e..0000000 --- a/src/main/kotlin/net/revanced/patcher/util/Jar2ASM.kt +++ /dev/null @@ -1,18 +0,0 @@ -package net.revanced.patcher.util - -import org.objectweb.asm.tree.ClassNode -import java.io.InputStream -import java.util.jar.JarInputStream - -object Jar2ASM { - fun jar2asm(input: InputStream): Map { - return buildMap { - val jar = JarInputStream(input) - var e = jar.nextJarEntry - while (e != null) { - TODO("Read jar file ...") - e = jar.nextJarEntry - } - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/net/revanced/patcher/util/PatternScanner.kt b/src/main/kotlin/net/revanced/patcher/util/PatternScanner.kt new file mode 100644 index 0000000..46a53ce --- /dev/null +++ b/src/main/kotlin/net/revanced/patcher/util/PatternScanner.kt @@ -0,0 +1,11 @@ +package net.revanced.patcher.util + +import org.objectweb.asm.tree.ClassNode + +class PatternScanner (private val classes: Array) { + companion object { + fun scan(){ + + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/net/revanced/patcher/PatcherTest.kt b/src/test/kotlin/net/revanced/patcher/PatcherTest.kt index aab519e..1504abb 100644 --- a/src/test/kotlin/net/revanced/patcher/PatcherTest.kt +++ b/src/test/kotlin/net/revanced/patcher/PatcherTest.kt @@ -2,14 +2,29 @@ package net.revanced.patcher import net.revanced.patcher.patch.Patch import net.revanced.patcher.patch.PatchResultError +import net.revanced.patcher.patch.PatchResultSuccess +import net.revanced.patcher.signature.SignatureLoader import org.junit.jupiter.api.Test internal class PatcherTest { @Test fun template() { - val adRemoverPatch = Patch { + val patcher = Patcher.loadFromFile( + "some.apk", + SignatureLoader.LoadFromJson("signatures.json").toMutableList() + ) - PatchResultError("") - } + val patches = mutableListOf( + Patch ("RemoveVideoAds") { + val videoAdShowMethodInstr = patcher.cache.Methods["SomeMethod"]?.instructions + PatchResultSuccess() + }, + Patch ("TweakLayout") { + val layoutMethod = patcher.cache.Methods["SomeMethod2"] + PatchResultError("Failed") + } + ) + + patcher.executePatches() } } \ No newline at end of file