This commit is contained in:
HolographicHat 2024-08-09 17:46:59 +08:00
parent 3723f39bd7
commit 2e52b58f98
No known key found for this signature in database
GPG Key ID: 12C8B5B85E5CE5C3
5 changed files with 21015 additions and 95 deletions

20989
app/proguard-dict.txt Normal file

File diff suppressed because it is too large Load Diff

View File

@ -15,6 +15,10 @@
# Uncomment this to preserve the line number information for # Uncomment this to preserve the line number information for
# debugging stack traces. # debugging stack traces.
-obfuscationdictionary proguard-dict.txt
-classobfuscationdictionary proguard-dict.txt
-packageobfuscationdictionary proguard-dict.txt
-keep class hat.holo.**{*;} -keep class hat.holo.**{*;}
-keepattributes SourceFile,LineNumberTable -keepattributes SourceFile,LineNumberTable

View File

@ -6,7 +6,9 @@ import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.util.ArrayMap import android.util.ArrayMap
import dalvik.system.DexClassLoader import dalvik.system.DexClassLoader
import hat.holo.token.utils.setAccess import hat.holo.token.utils.visitAndSetField
import hat.holo.token.utils.visitField
import hat.holo.token.utils.visitParentField
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
class LoaderActivity : Activity() { class LoaderActivity : Activity() {
@ -30,14 +32,10 @@ class LoaderActivity : Activity() {
// platform/frameworks/base/+/master/core/java/android/app/ActivityThread.java // platform/frameworks/base/+/master/core/java/android/app/ActivityThread.java
@SuppressLint("DiscouragedPrivateApi") @SuppressLint("DiscouragedPrivateApi")
private fun setClassLoader(classLoader: ClassLoader): ClassLoader { private fun setClassLoader(classLoader: ClassLoader): ClassLoader {
val mainThread = Activity::class.java.getDeclaredField("mMainThread").setAccess().get(this) val actThread = this.visitParentField<Activity, Any>("mMainThread")
val apks = mainThread.javaClass.getDeclaredField("mPackages").setAccess().get(mainThread) as ArrayMap<*, *> val pkgs = actThread.visitField<ArrayMap<String, WeakReference<*>>>("mPackages")
val apkRef = apks[packageName] as WeakReference<*> val pkg = pkgs[packageName]?.get() ?: throw IllegalStateException("WeakRef<LoadedApk> is null!")
val loadedApk = apkRef.get() ?: throw IllegalStateException("WeakRef<LoadedApk> is null!") return pkg.visitAndSetField<ClassLoader>("mClassLoader", classLoader)
val fClassLoader = loadedApk.javaClass.getDeclaredField("mClassLoader").setAccess()
val oClassLoader = fClassLoader.get(loadedApk)
fClassLoader.set(loadedApk, classLoader)
return oClassLoader as ClassLoader
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {

View File

@ -1,84 +0,0 @@
package hat.holo.token.utils
import android.content.Context
import de.robv.android.xposed.XC_MethodHook
import de.robv.android.xposed.XposedBridge
import de.robv.android.xposed.XposedHelpers
import java.lang.reflect.Method
import kotlin.time.DurationUnit
import kotlin.time.ExperimentalTime
import kotlin.time.measureTimedValue
class ConstraintSetWrapper(
clazz: Class<*>,
private val clone: Method,
private val apply: Method,
private val connect: Method,
private val connectWithMargin: Method
) {
private val instance = clazz.newInstance()
fun clone(constraintLayout: Any) {
clone.invoke(instance, constraintLayout)
}
fun connect(startID: Int, startSide: Int, endID: Int, endSide: Int) {
connect.invoke(instance, startID, startSide, endID, endSide)
}
fun connect(startID: Int, startSide: Int, endID: Int, endSide: Int, margin: Int) {
connectWithMargin.invoke(instance, startID, startSide, endID, endSide, margin)
}
fun applyTo(constraintLayout: Any) {
apply.invoke(instance, constraintLayout)
}
}
@OptIn(ExperimentalTime::class)
fun ClassLoader.createConstraintSet(ctx: Context) = measureTimedValue {
runCatching {
val calledMethods = arrayListOf<Method>()
val zRadialViewGroup = loadClass("com.google.android.material.timepicker.RadialViewGroup")
val zConstraints = loadClass("androidx.constraintlayout.widget.Constraints")
val zConstraintLayout = loadClass("androidx.constraintlayout.widget.ConstraintLayout")
val zConstraintSet = zConstraints.getDeclaredMethod("getConstraintSet").returnType
val hooks = zConstraintSet.declaredMethods.filter { method ->
method.parameterCount == 1 && method.parameterTypes[0] == zConstraintLayout
}.map { method ->
XposedHelpers.findAndHookMethod(
zConstraintSet,
method.name,
zConstraintLayout,
object : XC_MethodHook() {
override fun afterHookedMethod(param: MethodHookParam) {
calledMethods += param.method as Method
}
})
}
val oRadialViewGroup = zRadialViewGroup.getDeclaredConstructor(Context::class.java).apply {
isAccessible = true
}.newInstance(ctx)
zRadialViewGroup.getDeclaredMethod("updateLayoutParams").invoke(oRadialViewGroup)
hooks.forEach { hook -> hook.unhook() }
if (calledMethods.size == 2) {
ConstraintSetWrapper(
clazz = zConstraintSet,
clone = calledMethods[0],
apply = calledMethods[1],
connect = zConstraintSet.declaredMethods.first { method ->
method.parameterCount == 4 && method.parameterTypes.all { p -> p == Int::class.java }
},
connectWithMargin = zConstraintSet.declaredMethods.first { method ->
method.parameterCount == 5 && method.parameterTypes.all { p -> p == Int::class.java }
}
)
} else null
}.onFailure { ex ->
XposedBridge.log(ex)
}.getOrNull()
}.let { (result, duration) ->
XposedBridge.log("createConstraintSet cost ${duration.toString(DurationUnit.MILLISECONDS, 2)}")
result
}

View File

@ -13,9 +13,22 @@ fun <R> Any.invokeMethod(methodName: String, vararg args: Any?) : R {
return method.invoke(this, *args) as R return method.invoke(this, *args) as R
} }
@Suppress("UNCHECKED_CAST") inline fun <reified T> Any.visitField(fieldName: String) : T {
fun <T> Any.visitField(fieldName: String) : T {
val field = this.javaClass.getDeclaredField(fieldName) val field = this.javaClass.getDeclaredField(fieldName)
field.isAccessible = true field.isAccessible = true
return field.get(this) as T return field.get(this) as T
} }
inline fun <reified T, reified R> T.visitParentField(fieldName: String) : R {
val field = T::class.java.getDeclaredField(fieldName)
field.isAccessible = true
return field.get(this) as R
}
inline fun <reified T> Any.visitAndSetField(fieldName: String, value: Any) : T {
val field = this.javaClass.getDeclaredField(fieldName)
field.isAccessible = true
val old = field.get(this) as T
field.set(this, value)
return old
}