diff --git a/library/src/main/java/com/oasisfeng/hack/Hack.java b/library/src/main/java/com/oasisfeng/hack/Hack.java index 2742593..03694f9 100644 --- a/library/src/main/java/com/oasisfeng/hack/Hack.java +++ b/library/src/main/java/com/oasisfeng/hack/Hack.java @@ -2,6 +2,7 @@ package com.oasisfeng.hack; import android.util.ArrayMap; import android.util.Log; +import android.util.Pair; import com.oasisfeng.android.util.Supplier; import com.oasisfeng.android.util.Suppliers; @@ -32,6 +33,10 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; +import static android.os.Build.VERSION.SDK_INT; +import static android.os.Build.VERSION_CODES.O; +import static java.lang.Character.toLowerCase; + /** * Java reflection helper optimized for hacking non-public APIs. * The core design philosophy behind is compile-time consistency enforcement. @@ -60,6 +65,7 @@ public class Hack { *
* interface HiddenClass extends Mirror<com.foo.HiddenClass> { * void foo(int x); + * int getValue(); // Mirror getter for member field "value" (setter can be defined too) * static int bar(String name) { * return Hack.mirrorStaticMethod("bar", -1, name); * } @@ -196,7 +202,8 @@ public class Hack { for (final Method mirror_method : inner_class.getMethods()) try { findSourceMethodForMirror(mirror_method, source_class); } catch (final NoSuchMethodException e) { - fail(new AssertionException(e)); + if (extractFieldGetterOrSetterFromMethod(mirror_method, source_class) != null) continue; + fail(new AssertionException(e).setHackedClass(into(inner_class))); } verifyAllMirrorsIn(inner_class); // Only Mirror class may contain inner Mirror classes } @@ -214,7 +221,19 @@ public class Hack { final Method source_method = findSourceMethodForMirror(mirror_method, source_class); // TODO: Cache source_method.setAccessible(true); source_result = source_method.invoke(mSource, args); - } catch (final IllegalAccessException/* should not happen */| NoSuchMethodException e) { + } catch (final NoSuchMethodException e) { + final String mirror_method_name = mirror_method.getName(); final boolean is_getter; final char first_char; + final Pairaccessor = extractFieldGetterOrSetterFromMethod(mirror_method, source_class); // TODO: Cache + if (accessor != null) { + final Field field = accessor.second; + field.setAccessible(true); + if (! accessor.first) { // Setter + field.set(mSource, args[0]); + return null; + } else return field.get(mSource); + } + return fallback(mirror_method); + } catch (final IllegalAccessException e) { return fallback(mirror_method); } catch (final InvocationTargetException e) { throw e.getTargetException(); @@ -263,6 +282,25 @@ public class Hack { return source_class.getDeclaredMethod(mirror_method.getName(), mirror_method.getParameterTypes()); } + private static @Nullable Pair extractFieldGetterOrSetterFromMethod(final Method mirror_method, final Class> source_class) { + final String mirror_method_name = mirror_method.getName(); final char first_char; + if (mirror_method_name.length() > 3 && Character.isUpperCase(first_char = mirror_method_name.charAt(3))) { + final boolean is_getter = mirror_method_name.startsWith("get"); + if (is_getter) { + if (getParameterCount(mirror_method) != 0) return null; // Getter should have no parameter + } else if (! mirror_method_name.startsWith("set") || getParameterCount(mirror_method) != 1) return null; + try { + final Field field = source_class.getDeclaredField(toLowerCase(first_char) + mirror_method_name.substring(4)); + return new Pair<>(is_getter, field); + } catch (final NoSuchFieldException ignored) {} + } + return null; + } + + private static int getParameterCount(final Method mirror_method) { + return SDK_INT >= O ? mirror_method.getParameterCount() : mirror_method.getParameterTypes().length; + } + public static class AssertionException extends Throwable { private HackedClass> mClass;