diff --git a/src/main/kotlin/app/revanced/patcher/Patcher.kt b/src/main/kotlin/app/revanced/patcher/Patcher.kt index d9f114a..cd33ee7 100644 --- a/src/main/kotlin/app/revanced/patcher/Patcher.kt +++ b/src/main/kotlin/app/revanced/patcher/Patcher.kt @@ -82,15 +82,16 @@ class Patcher(private val options: PatcherOptions) { if (result == null) { logger.trace("Merging type $type") classes.add(classDef) - } else { - val (existingClass, existingClassIndex) = result + continue + } - logger.trace("Type $type exists. Adding missing methods and fields.") + val (existingClass, existingClassIndex) = result - existingClass.merge(classDef, context, logger).let { mergedClass -> - if (mergedClass !== existingClass) // referential equality check - classes[existingClassIndex] = mergedClass - } + logger.trace("Type $type exists. Adding missing methods and fields.") + + existingClass.merge(classDef, context, logger).let { mergedClass -> + if (mergedClass !== existingClass) // referential equality check + classes[existingClassIndex] = mergedClass } } } diff --git a/src/main/kotlin/app/revanced/patcher/util/ClassMerger.kt b/src/main/kotlin/app/revanced/patcher/util/ClassMerger.kt index a3f41e4..c1ffd3c 100644 --- a/src/main/kotlin/app/revanced/patcher/util/ClassMerger.kt +++ b/src/main/kotlin/app/revanced/patcher/util/ClassMerger.kt @@ -4,13 +4,16 @@ import app.revanced.patcher.PatcherContext import app.revanced.patcher.extensions.or import app.revanced.patcher.logging.Logger import app.revanced.patcher.util.ClassMerger.Utils.asMutableClass +import app.revanced.patcher.util.ClassMerger.Utils.filterAny import app.revanced.patcher.util.ClassMerger.Utils.filterNotAny import app.revanced.patcher.util.ClassMerger.Utils.isPublic import app.revanced.patcher.util.ClassMerger.Utils.toPublic import app.revanced.patcher.util.TypeUtil.traverseClassHierarchy import app.revanced.patcher.util.proxy.mutableTypes.MutableClass import app.revanced.patcher.util.proxy.mutableTypes.MutableClass.Companion.toMutable +import app.revanced.patcher.util.proxy.mutableTypes.MutableField import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable +import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable import org.jf.dexlib2.AccessFlags import org.jf.dexlib2.iface.ClassDef @@ -30,6 +33,8 @@ internal object ClassMerger { * @param logger A logger. */ fun ClassDef.merge(otherClass: ClassDef, context: PatcherContext, logger: Logger? = null) = this + .fixFieldAccess(otherClass, logger) + .fixMethodAccess(otherClass, logger) .addMissingFields(otherClass, logger) .addMissingMethods(otherClass, logger) .publicize(otherClass, context, logger) @@ -97,6 +102,64 @@ internal object ClassMerger { } else this + /** + * Publicize fields if they are public in [reference]. + * + * @param reference The class to check the [AccessFlags] of the fields in. + * @param logger A logger. + */ + private fun ClassDef.fixFieldAccess(reference: ClassDef, logger: Logger? = null): ClassDef { + val brokenFields = fields.filterAny(reference.fields) { field, referenceField -> + if (field.name != referenceField.name) return@filterAny false + + referenceField.accessFlags.isPublic() && !field.accessFlags.isPublic() + } + + if (brokenFields.isEmpty()) return this + + logger?.trace("Found ${brokenFields.size} broken fields") + + /** + * Make a field public. + */ + fun MutableField.publicize() { + accessFlags = accessFlags.toPublic() + } + + return asMutableClass().apply { + fields.filter { brokenFields.contains(it) }.forEach(MutableField::publicize) + } + } + + /** + * Publicize methods if they are public in [reference]. + * + * @param reference The class to check the [AccessFlags] of the methods in. + * @param logger A logger. + */ + private fun ClassDef.fixMethodAccess(reference: ClassDef, logger: Logger? = null): ClassDef { + val brokenMethods = methods.filterAny(reference.methods) { method, referenceMethod -> + if (!MethodUtil.methodSignaturesMatch(method, referenceMethod)) return@filterAny false + + referenceMethod.accessFlags.isPublic() && !method.accessFlags.isPublic() + } + + if (brokenMethods.isEmpty()) return this + + logger?.trace("Found ${brokenMethods.size} methods") + + /** + * Make a method public. + */ + fun MutableMethod.publicize() { + accessFlags = accessFlags.toPublic() + } + + return asMutableClass().apply { + methods.filter { brokenMethods.contains(it) }.forEach(MutableMethod::publicize) + } + } + private object Utils { fun ClassDef.asMutableClass() = if (this is MutableClass) this else this.toMutable() @@ -114,6 +177,18 @@ internal object ClassMerger { */ fun Int.toPublic() = this.or(AccessFlags.PUBLIC).and(AccessFlags.PRIVATE.value.inv()) + /** + * Filter [this] on [needles] matching the given [predicate]. + * + * @param this The hay to filter for [needles]. + * @param needles The needles to filter [this] with. + * @param predicate The filter. + * @return The [this] filtered on [needles] matching the given [predicate]. + */ + fun Iterable.filterAny( + needles: Iterable, predicate: (HayType, NeedleType) -> Boolean + ) = Iterable::filter.any(this, needles, predicate) + /** * Filter [this] on [needles] not matching the given [predicate]. *