pygmalion
это автономная non-root
android
библиотека, необходимая для кастомизации UI android-приложений
посредством создания хуков для AssetManager
и Activity
Поддерживаются устройства с Android 5.0 - Android 15+
и x86
, x86_64
, arm32
, и arm64
архитектурами.
Caution
Я и контрибьютеры не несём ответственности за любой причинённый вред, вызванный использованием этой библиотеки в ваших проектах. Также этот проект находится в альфа-стадии, поэтому обратная совместимость с будущими обновлениями не гарантируется.
Изначально это была странная идея создания средства для кастомизации приложений без использования root
, xposed
и т.п.,
и я не планировал использовать натив. Но, как оказалось, это был единственный нормальный
способ осуществить мою задумку перехвата вызовов AssetManager
. Помимо этого я не хочу использовать какой-нибудь известный
ART hooking framework
из-за его нестабильности по сравнению с моим методом.
pygmalion
нужен для тех случаев, когда требуется реализовать кастомизацию приложений
(пр. изменение акцентных цветов) без исходного кода.
Чтобы использовать эту библиотеку, нужно знать про комплексные и примитивные (хранящие простые значения) ресурсы
(см. App resources overview).
Также вы можете ознакомиться с исходным кодом AssetManager,
Resources,
TypedValue
и TypedArray
для определённых платформ.
pygmalion
работает на двух уровнях:
- Java-уровень предоставляет API для создания хуков для
AssetManager
иActivity
. - Нативный уровень используется для перехвата собственных нативных функций
AssetManager
, связанных с ресурсами, путем перерегистрации оных, для дальнейших манипуляций с извлеченными данными (идентификаторами атрибутов, значениями ресурсов и т. д.) до того, как они будут возвращены в точку вызова.
На нативном уровне pygmalion
перехватывает функцию, которая используется для регистрации всех JNI
функций AssetManager
c сигнатурой
_ZN7android37register_android_content_AssetManagerEP7_JNIEnv
Затем целевые функции находятся из дампа по жестко-закодированным именам и сигнатурам и заменяются на функции-хуки, которые вызывают
Java-функции с реализацией логики. Этот способ более стабильный, чем многие ART hooking
фреймфорки, потому что там
используется встроенный JNI
API и проверенные хукинг-библиотеки.
Note
Для Android N pygmalion
отключает @FastNative
оптимизации для всех функций AssetManager
,
потому что это приводит к сбоям.
- Добавить хуки для
LayoutInflater
- Вы можете хукать только
AssetManager
иActivity
. - В хуках
AssetManager
вы не можете добавлять/удалять атрибуты
library
- "ядро"pygmalion
со всеми API нужными для разработки.demo
- android-приложение, разработанное как "галерея" для демонстрации.stub
- обеспечивает прямой доступ к приватнымandroid
api
.
Я настоятельно рекомендую сто раз подумать, прежде чем использовать эту библиотеку в своих проектах. Она изначально предназначена не для нормальной android-разработки, а для случаев когда нет доступа к исходному коду. Например, для разработки модификаций android-приложений таких как Revanced's mods, VTLIte и т.д.
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
}
dependencies {
implementation(
fileTree("libs") {
include("*.jar", "*.aar")
}
)
}
pygmalion
имеет API для ручного управления:
/*
* проверяем проинициализировался ли Pygmalion
* и включаем хукинг
*/
Pygmalion.initialize()
if(Pygmalion.isInitialized()) {
Pyhmalion.hook()
}
//отключаем хукинг
Pygmalion.unhook()
Kotlin:
@JvmField
val exampleHook =
assetHook {
onLoadResourceValue { asset ->
//etc
}
onLoadThemeAttrValue { asset ->
//etc
}
onApplyStyle { assets ->
//etc
}
onResolveAttrs { assets ->
//etc
}
onRetrieveAttrs { assets ->
//etc
}
}
fun hook() {
//Добавляем хуки для AssetManager
Pygmalion.registerAssetHooks(exampleHook,/*...*/)
}
fun unhook() {
//Удаляем хуки для AssetManager
Pygmalion.unregisterAssetHooks(exampleHook)
}
Java:
public class HooksExample {
private static IAssetHook hook = new IAssetHook() {
@Override
public void onLoadResourceValueHook(int resId, @NotNull TypedValue outValue, boolean resolveAttrs) {
//etc
}
@Override
public void onLoadThemeAttrValueHook(int resId, @NotNull TypedValue outValue, boolean resolveAttrs) {
//etc
}
@Override
public void onApplyStyleHook(int defStyleAttr, int defStyleRes, @NotNull int[] inAttrs, @NotNull List<TypedValue> outValues) {
//etc
}
@Override
public void onResolveAttrsHook(int defStyleAttr, int defStyleRes, @NotNull int[] inValues, @NotNull int[] inAttrs, @NotNull List<TypedValue> outValues) {
//etc
}
@Override
public void onRetrieveAttrsHook(@NotNull int[] inAttrs, @NotNull List<TypedValue> outValues) {
//etc
}
};
public static void hook() {
//Добавляем хуки для AssetManager
Pygmalion.registerAssetHooks(hook,hook);
}
public static void unhook() {
//Удаляем хуки для AssetManager
Pygmalion.unregisterAssetHooks(hook,hook);
}
}
Kotlin:
@JvmField
val exampleHook = activityLifecycleCallbacks {
onActivityCreated { activity, bundle : Bundle? ->
//etc
}
onActivityResumed { activity ->
//etc
}
//etc
}
fun hook() {
//Добавляем хуки для Activity
Pygmalion.registerActivityLifecycleCallbacks(exampleHook,/*...*/)
}
fun unhook() {
//Удаляем хуки для Activity
Pygmalion.unregisterActivityLifecycleCallbacks(exampleHook,/*...*/)
}
Java:
public class HooksExample {
private static IActivityLifecycleCallback hook = new IActivityLifecycleCallback() {
@Override
public void onActivityCreated(@NotNull Activity activity, @Nullable Bundle savedInstanceState) {
//etc
}
@Override
public void onActivityResumed(@NotNull Activity activity) {
//etc
}
//etc
};
public static void hook() {
//Добавляем хуки для Activity
Pygmalion.registerActivityLifecycleCallbacks(hook,hook);
}
public static void unhook() {
//Удаляем хуки для Activity
Pygmalion.unregisterActivityLifecycleCallbacks(hook,hook);
}
}