package com.lsposed.lspatch;
import android.app.ActivityThread;
import android.app.Application;
import android.app.LoadedApk;
import android.content.pm.ApplicationInfo;
import android.content.res.CompatibilityInfo;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import com.lsposed.lspatch.databinding.ActivityMainBinding;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;
import java.util.zip.ZipFile;
import de.robv.android.xposed.XposedHelpers;
public class MainActivity extends AppCompatActivity {
static {
System.loadLibrary("lspatch001");
}
private ActivityMainBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
TextView tv = binding.sampleText;
tv.setText(stringFromJNI());
}
public native String stringFromJNI();
public void onTest1(View view) {
loadApk0(this);
}
@Override
protected void onStop() {
super.onStop();
}
public void loadOutApk() {
try {
ActivityThread activityThread = ActivityThread.currentActivityThread();
var mBoundApplication = XposedHelpers.getObjectField(activityThread, "mBoundApplication");
LoadedApk stubLoadedApk = (LoadedApk) XposedHelpers.getObjectField(mBoundApplication, "info");
var appInfo = (ApplicationInfo) XposedHelpers.getObjectField(mBoundApplication, "appInfo");
var compatInfo = (CompatibilityInfo) XposedHelpers.newInstance(CompatibilityInfo.class);
var baseClassLoader = stubLoadedApk.getClassLoader();
Path originPath = Paths.get(appInfo.dataDir, "cache/origin/");
Path cacheApkPath;
try (ZipFile sourceFile = new ZipFile(appInfo.sourceDir)) {
cacheApkPath = originPath.resolve(sourceFile.getEntry("assets/aspplication-debug.apk").getCrc() + ".apk");
}
String sourceDir = appInfo.sourceDir;
String publicSourceDir = appInfo.publicSourceDir;
appInfo.sourceDir = cacheApkPath.toString();
appInfo.publicSourceDir = cacheApkPath.toString();
String nativeLibraryDir = appInfo.nativeLibraryDir;
appInfo.nativeLibraryDir = cacheApkPath.toString() + "!/lib/arm64-v8a";
String className = appInfo.className;
String name = appInfo.name;
appInfo.className = "com.aspirin.aspplication.BaseApplication";
appInfo.name = "com.aspirin.aspplication.BaseApplication";
if (!Files.exists(cacheApkPath)) {
Files.createDirectories(originPath);
try (InputStream is = baseClassLoader.getResourceAsStream("assets/aspplication-debug.apk")) {
Files.copy(is, cacheApkPath);
}
}
cacheApkPath.toFile().setWritable(false);
Map<?, ?> mPackages = (Map<?, ?>) XposedHelpers.getObjectField(activityThread, "mPackages");
mPackages.remove(appInfo.packageName);
LoadedApk outAppLoadedApk = activityThread.getPackageInfoNoCheck(appInfo, compatInfo);
XposedHelpers.setObjectField(outAppLoadedApk, "mBaseClassLoader", baseClassLoader);
LspModuleClassLoader lspModuleClassLoader = new LspModuleClassLoader(cacheApkPath.toString(), appInfo.sourceDir, appInfo.nativeLibraryDir, baseClassLoader);
XposedHelpers.setObjectField(outAppLoadedApk, "mClassLoader", lspModuleClassLoader);
ClassLoader outClassLoader = outAppLoadedApk.getClassLoader();
XposedHelpers.setObjectField(mBoundApplication, "info", outAppLoadedApk);
Application outApplication = (Application) XposedHelpers.callMethod(outAppLoadedApk, "makeApplication", false, XposedHelpers.getObjectField(activityThread, "mInstrumentation"));
appInfo.sourceDir = sourceDir;
appInfo.publicSourceDir = publicSourceDir;
appInfo.nativeLibraryDir = nativeLibraryDir;
appInfo.className = className;
appInfo.name = name;
Class<?> ToastUtilsClass = outClassLoader.loadClass("com.aspirin.aspplication.ToastUtils");
Method showToast = ToastUtilsClass.getDeclaredMethod("showActivity");
showToast.invoke(null);
} catch (Throwable e) {
System.out.println("loadOutApk throwable: " + e);
e.printStackTrace();
}
}
private void loadApk0(MainActivity mainActivity) {
loadOutApk();
}
}