diff --git a/core/src/main/java/de/robv/android/xposed/XposedInit.java b/core/src/main/java/de/robv/android/xposed/XposedInit.java index 71bc65af..a601e7dc 100644 --- a/core/src/main/java/de/robv/android/xposed/XposedInit.java +++ b/core/src/main/java/de/robv/android/xposed/XposedInit.java @@ -39,6 +39,7 @@ import android.os.IBinder; import android.os.Process; import android.util.Log; +import org.lsposed.lspd.impl.LSPosedContext; import org.lsposed.lspd.models.PreLoadedApk; import org.lsposed.lspd.nativebridge.NativeAPI; import org.lsposed.lspd.nativebridge.ResourcesHook; @@ -57,6 +58,9 @@ import java.util.concurrent.atomic.AtomicBoolean; import de.robv.android.xposed.callbacks.XC_InitPackageResources; import de.robv.android.xposed.callbacks.XCallback; import hidden.HiddenApiBridge; +import io.github.libxposed.XposedInterface; +import io.github.libxposed.XposedModuleInterface; +import io.github.libxposed.XposedResource; public final class XposedInit { private static final String TAG = XposedBridge.TAG; @@ -196,7 +200,7 @@ public final class XposedInit { } // Replace the returned resources with our subclass. - XResources newRes = new XResources( + var newRes = new XposedResource( (ClassLoader) XposedHelpers.getObjectField(param.getResult(), "mClassLoader")); HiddenApiBridge.Resources_setImpl(newRes, (ResourcesImpl) XposedHelpers.getObjectField(param.getResult(), "mResourcesImpl")); newRes.initObject(resDir); @@ -208,6 +212,11 @@ public final class XposedInit { resparam.packageName = packageName; resparam.res = newRes; XCallback.callAll(resparam); + + var rlparam = new XposedModuleInterface.ResourceLoadedParam(); + rlparam.packageName = packageName; + rlparam.res = newRes; + LSPosedContext.callOnResourceLoaded(rlparam, null); } param.setResult(newRes); diff --git a/core/src/main/java/io/github/libxposed/XposedContext.java b/core/src/main/java/io/github/libxposed/XposedContext.java index 95b5b35e..321aeae4 100644 --- a/core/src/main/java/io/github/libxposed/XposedContext.java +++ b/core/src/main/java/io/github/libxposed/XposedContext.java @@ -2,7 +2,6 @@ package io.github.libxposed; import android.content.Context; - public abstract class XposedContext extends Context implements XposedInterface { } diff --git a/core/src/main/java/io/github/libxposed/XposedContextWrapper.java b/core/src/main/java/io/github/libxposed/XposedContextWrapper.java index 27673d17..d792758c 100644 --- a/core/src/main/java/io/github/libxposed/XposedContextWrapper.java +++ b/core/src/main/java/io/github/libxposed/XposedContextWrapper.java @@ -10,15 +10,25 @@ public class XposedContextWrapper extends ContextWrapper implements XposedInterf } @Override - public XposedContext getBaseContext() { + final public XposedContext getBaseContext() { return (XposedContext) super.getBaseContext(); } @Override - public void hook() { + final public void hook() { getBaseContext().hook(); } + @Override + final public void log(String message) { + getBaseContext().log(message); + } + + @Override + final public void log(String message, Throwable throwable) { + getBaseContext().log(message, throwable); + } + @Override final protected void attachBaseContext(Context base) { if (base instanceof XposedContext) { diff --git a/core/src/main/java/io/github/libxposed/XposedInterface.java b/core/src/main/java/io/github/libxposed/XposedInterface.java index b3e6ed8b..9f5052e0 100644 --- a/core/src/main/java/io/github/libxposed/XposedInterface.java +++ b/core/src/main/java/io/github/libxposed/XposedInterface.java @@ -2,4 +2,6 @@ package io.github.libxposed; public interface XposedInterface { void hook(); + void log(String message); + void log(String message, Throwable throwable); } diff --git a/core/src/main/java/io/github/libxposed/XposedModuleInterface.java b/core/src/main/java/io/github/libxposed/XposedModuleInterface.java index f0906b88..8abf3a5d 100644 --- a/core/src/main/java/io/github/libxposed/XposedModuleInterface.java +++ b/core/src/main/java/io/github/libxposed/XposedModuleInterface.java @@ -1,11 +1,31 @@ package io.github.libxposed; +import android.content.pm.ApplicationInfo; +import android.os.Bundle; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +@SuppressWarnings("unused") public interface XposedModuleInterface { - default void onPackageLoaded() { + class PackageLoadedParam { + public String packageName; + public String processName; + public ClassLoader classLoader; + public ApplicationInfo appInfo; + public boolean isFirstApplication; + } + + class ResourceLoadedParam { + public String packageName; + public XposedResource res; + } + + default void onPackageLoaded(@NonNull PackageLoadedParam param, @Nullable Bundle extra) { } - default void onResourceLoaded() { + default void onResourceLoaded(@NonNull ResourceLoadedParam param, @Nullable Bundle extra) { } diff --git a/core/src/main/java/io/github/libxposed/XposedResource.java b/core/src/main/java/io/github/libxposed/XposedResource.java new file mode 100644 index 00000000..de4c0af6 --- /dev/null +++ b/core/src/main/java/io/github/libxposed/XposedResource.java @@ -0,0 +1,9 @@ +package io.github.libxposed; + +import android.content.res.XResources; + +public class XposedResource extends XResources { + public XposedResource(ClassLoader classLoader) { + super(classLoader); + } +} diff --git a/core/src/main/java/org/lsposed/lspd/hooker/LoadedApkGetCLHooker.java b/core/src/main/java/org/lsposed/lspd/hooker/LoadedApkGetCLHooker.java index 1607bbc3..1b8a7a11 100644 --- a/core/src/main/java/org/lsposed/lspd/hooker/LoadedApkGetCLHooker.java +++ b/core/src/main/java/org/lsposed/lspd/hooker/LoadedApkGetCLHooker.java @@ -27,6 +27,7 @@ import android.app.AndroidAppHelper; import android.app.LoadedApk; import android.os.IBinder; +import org.lsposed.lspd.impl.LSPosedContext; import org.lsposed.lspd.util.Hookers; import org.lsposed.lspd.util.MetaDataReader; import org.lsposed.lspd.util.Utils; @@ -40,6 +41,7 @@ import de.robv.android.xposed.XC_MethodReplacement; import de.robv.android.xposed.XposedBridge; import de.robv.android.xposed.XposedHelpers; import de.robv.android.xposed.callbacks.XC_LoadPackage; +import io.github.libxposed.XposedModuleInterface; public class LoadedApkGetCLHooker extends XC_MethodHook { private final LoadedApk loadedApk; @@ -95,6 +97,13 @@ public class LoadedApkGetCLHooker extends XC_MethodHook { Hookers.logD("Call handleLoadedPackage: packageName=" + lpparam.packageName + " processName=" + lpparam.processName + " isFirstApplication=" + isFirstApplication + " classLoader=" + lpparam.classLoader + " appInfo=" + lpparam.appInfo); XC_LoadPackage.callAll(lpparam); + var plparam = new XposedModuleInterface.PackageLoadedParam(); + plparam.packageName = packageName; + plparam.processName = processName; + plparam.classLoader = classLoader; + plparam.appInfo = loadedApk.getApplicationInfo(); + plparam.isFirstApplication = isFirstApplication; + LSPosedContext.callOnPackageLoaded(plparam, null); } catch (Throwable t) { Hookers.logE("error when hooking LoadedApk#getClassLoader", t); } finally { diff --git a/core/src/main/java/org/lsposed/lspd/impl/LSPosedContext.java b/core/src/main/java/org/lsposed/lspd/impl/LSPosedContext.java index 60e5a610..f4acefda 100644 --- a/core/src/main/java/org/lsposed/lspd/impl/LSPosedContext.java +++ b/core/src/main/java/org/lsposed/lspd/impl/LSPosedContext.java @@ -43,6 +43,8 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import de.robv.android.xposed.XposedHelpers; import de.robv.android.xposed.XposedInit; @@ -54,10 +56,34 @@ public class LSPosedContext extends XposedContext { public static final String TAG = "LSPosedContext"; - private final Context base; + static final Set modules = ConcurrentHashMap.newKeySet(); - LSPosedContext(Context base) { + private final Context base; + private final String packageName; + + LSPosedContext(Context base, String packageName) { this.base = base; + this.packageName = packageName; + } + + public static void callOnPackageLoaded(XposedModuleInterface.PackageLoadedParam param, Bundle extra) { + for (XposedModule module : modules) { + try { + module.onPackageLoaded(param, extra); + } catch (Throwable t) { + Log.e(TAG, "Error when calling onPackageLoaded of " + ((LSPosedContext) module.getBaseContext()).packageName, t); + } + } + } + + public static void callOnResourceLoaded(XposedModuleInterface.ResourceLoadedParam param, Bundle extra) { + for (XposedModule module : modules) { + try { + module.onResourceLoaded(param, extra); + } catch (Throwable t) { + Log.e(TAG, "Error when calling onResourceLoaded of " + ((LSPosedContext) module.getBaseContext()).packageName, t); + } + } } public static boolean loadModules(ActivityThread at, Module module) { @@ -104,7 +130,7 @@ public class LSPosedContext extends XposedContext { } args[i] = null; } - var ctx = new LSPosedContext((Context) ctor.newInstance(args)); + var ctx = new LSPosedContext((Context) ctor.newInstance(args), module.packageName); for (var entry : module.file.moduleClassNames) { var moduleClass = ctx.getClassLoader().loadClass(entry); Log.d(TAG, " Loading class " + moduleClass); @@ -112,11 +138,12 @@ public class LSPosedContext extends XposedContext { Log.e(TAG, " This class doesn't implement any sub-interface of XposedModule, skipping it"); } try { - if (moduleClass.getMethod("onResourceLoaded").getDeclaringClass() != XposedModuleInterface.class) { + if (moduleClass.getMethod("onResourceLoaded", XposedModuleInterface.ResourceLoadedParam.class, Bundle.class).getDeclaringClass() != XposedModuleInterface.class) { XposedInit.hookResources(); } var moduleEntry = moduleClass.getConstructor(XposedContext.class); - moduleEntry.newInstance(ctx); + var moduleContext = (XposedModule) moduleEntry.newInstance(ctx); + modules.add(moduleContext); } catch (Throwable e) { Log.e(TAG, " Failed to load class " + moduleClass, e); } @@ -657,4 +684,14 @@ public class LSPosedContext extends XposedContext { public void hook() { throw new UnsupportedOperationException("Not implemented"); } + + @Override + public void log(String message) { + Log.i(TAG, packageName + ": " + message); + } + + @Override + public void log(String message, Throwable throwable) { + Log.e(TAG, packageName + ": " + message, throwable); + } }