[core] Support zygisk (#1228)
Co-authored-by: 南宫雪珊 <vvb2060@gmail.com> Co-authored-by: tehcneko <88844448+tehcneko@users.noreply.github.com>
This commit is contained in:
parent
24c468164e
commit
7a7c0c56e3
|
|
@ -71,27 +71,40 @@ jobs:
|
|||
echo 'org.gradle.vfs.watch=true' >> gradle.properties
|
||||
echo 'org.gradle.jvmargs=-Xmx2048m' >> gradle.properties
|
||||
echo 'android.native.buildOutput=verbose' >> gradle.properties
|
||||
./gradlew zipRelease zipDebug
|
||||
./gradlew zipAll
|
||||
ccache -s
|
||||
- name: Prepare artifact
|
||||
if: success()
|
||||
id: prepareArtifact
|
||||
run: |
|
||||
releaseName=`ls core/release/LSPosed-v*-release.zip | awk -F '(/|.zip)' '{print $3}'` && echo "::set-output name=releaseName::$releaseName"
|
||||
debugName=`ls core/release/LSPosed-v*-debug.zip | awk -F '(/|.zip)' '{print $3}'` && echo "::set-output name=debugName::$debugName"
|
||||
unzip core/release/LSPosed-v*-release.zip -d LSPosed-release
|
||||
unzip core/release/LSPosed-v*-debug.zip -d LSPosed-debug
|
||||
- name: Upload release
|
||||
riruReleaseName=`ls core/release/LSPosed-riru-v*-release.zip | awk -F '(/|.zip)' '{print $3}'` && echo "::set-output name=riruReleaseName::$riruReleaseName"
|
||||
riruDebugName=`ls core/release/LSPosed-riru-v*-debug.zip | awk -F '(/|.zip)' '{print $3}'` && echo "::set-output name=riruDebugName::$riruDebugName"
|
||||
zygiskReleaseName=`ls core/release/LSPosed-zygisk-v*-release.zip | awk -F '(/|.zip)' '{print $3}'` && echo "::set-output name=zygiskReleaseName::$zygiskReleaseName"
|
||||
zygiskDebugName=`ls core/release/LSPosed-zygisk-v*-debug.zip | awk -F '(/|.zip)' '{print $3}'` && echo "::set-output name=zygiskDebugName::$zygiskDebugName"
|
||||
unzip core/release/LSPosed-riru-v*-release.zip -d LSPosed-riru-release
|
||||
unzip core/release/LSPosed-riru-v*-debug.zip -d LSPosed-riru-debug
|
||||
unzip core/release/LSPosed-zygisk-v*-release.zip -d LSPosed-zygisk-release
|
||||
unzip core/release/LSPosed-zygisk-v*-debug.zip -d LSPosed-zygisk-debug
|
||||
- name: Upload riru release
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ${{ steps.prepareArtifact.outputs.releaseName }}
|
||||
path: './LSPosed-release/*'
|
||||
- name: Upload debug
|
||||
# if: ${{ github.event_name == 'pull_request' && success() }}
|
||||
name: ${{ steps.prepareArtifact.outputs.riruReleaseName }}
|
||||
path: './LSPosed-riru-release/*'
|
||||
- name: Upload riru debug
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ${{ steps.prepareArtifact.outputs.debugName }}
|
||||
path: './LSPosed-debug/*'
|
||||
name: ${{ steps.prepareArtifact.outputs.riruDebugName }}
|
||||
path: './LSPosed-riru-debug/*'
|
||||
- name: Upload zygisk release
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ${{ steps.prepareArtifact.outputs.zygiskReleaseName }}
|
||||
path: './LSPosed-zygisk-release/*'
|
||||
- name: Upload zygisk debug
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ${{ steps.prepareArtifact.outputs.zygiskDebugName }}
|
||||
path: './LSPosed-zygisk-debug/*'
|
||||
- name: Upload mappings
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
|
|
@ -108,7 +121,9 @@ jobs:
|
|||
COMMIT_URL: ${{ github.event.head_commit.url }}
|
||||
run: |
|
||||
OUTPUT="core/release/"
|
||||
export release=$(find $OUTPUT -name "LSPosed-v*-release.zip")
|
||||
export debug=$(find $OUTPUT -name "LSPosed-v*-debug.zip")
|
||||
export riruRelease=$(find $OUTPUT -name "LSPosed-riru-v*-release.zip")
|
||||
export riruDebug=$(find $OUTPUT -name "LSPosed-riru-v*-debug.zip")
|
||||
export zygiskRelease=$(find $OUTPUT -name "LSPosed-zygisk-v*-release.zip")
|
||||
export zygiskDebug=$(find $OUTPUT -name "LSPosed-zygisk-v*-debug.zip")
|
||||
ESCAPED=`python3 -c 'import json,os,urllib.parse; msg = json.dumps(os.environ["COMMIT_MESSAGE"]); print(urllib.parse.quote(msg if len(msg) <= 1024 else json.dumps(os.environ["COMMIT_URL"])))'`
|
||||
curl -v "https://api.telegram.org/bot${BOT_TOKEN}/sendMediaGroup?chat_id=${CHANNEL_ID}&media=%5B%7B%22type%22:%22document%22,%20%22media%22:%22attach://release%22%7D,%7B%22type%22:%22document%22,%20%22media%22:%22attach://debug%22,%22caption%22:${ESCAPED}%7D%5D" -F release="@$release" -F debug="@$debug"
|
||||
curl -v "https://api.telegram.org/bot${BOT_TOKEN}/sendMediaGroup?chat_id=${CHANNEL_ID}&media=%5B%7B%22type%22%3A%22document%22%2C%20%22media%22%3A%22attach%3A%2F%2FzygiskRelease%22%7D%2C%7B%22type%22%3A%22document%22%2C%20%22media%22%3A%22attach%3A%2F%2FriruRelease%22%7D%2C%7B%22type%22%3A%22document%22%2C%20%22media%22%3A%22attach%3A%2F%2FzygiskDebug%22%7D%2C%7B%22type%22%3A%22document%22%2C%20%22media%22%3A%22attach%3A%2F%2FriruDebug%22%2C%22caption%22:${ESCAPED}%7D%5D" -F riruRelease="@$riruRelease" -F riruDebug="@$riruDebug"
|
||||
|
|
|
|||
|
|
@ -342,6 +342,16 @@ public class ConfigManager {
|
|||
}
|
||||
}
|
||||
|
||||
public static List<String> getDenyListPackages() {
|
||||
List<String> list = new ArrayList<>();
|
||||
try {
|
||||
list.addAll(LSPManagerServiceHolder.getService().getDenyListPackages());
|
||||
} catch (RemoteException e) {
|
||||
Log.e(App.TAG, Log.getStackTraceString(e));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public static void flashZip(String zipPath, ParcelFileDescriptor outputStream) {
|
||||
try {
|
||||
LSPManagerServiceHolder.getService().flashZip(zipPath, outputStream);
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ public class AppHelper {
|
|||
|
||||
public static final String SETTINGS_CATEGORY = "de.robv.android.xposed.category.MODULE_SETTINGS";
|
||||
public static final int FLAG_SHOW_FOR_ALL_USERS = 0x0400;
|
||||
private static List<String> denyList;
|
||||
private static List<PackageInfo> appList;
|
||||
|
||||
public static Intent getSettingsIntent(String packageName, int userId) {
|
||||
|
|
@ -137,4 +138,11 @@ public class AppHelper {
|
|||
}
|
||||
return appList;
|
||||
}
|
||||
|
||||
public static List<String> getDenyList(boolean force) {
|
||||
if (denyList == null || force) {
|
||||
denyList = ConfigManager.getDenyListPackages();
|
||||
}
|
||||
return denyList;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -105,6 +105,7 @@ public class ScopeAdapter extends RecyclerView.Adapter<ScopeAdapter.ViewHolder>
|
|||
private final HashSet<ApplicationWithEquals> checkedList = new HashSet<>();
|
||||
private final ConcurrentLinkedQueue<AppInfo> searchList = new ConcurrentLinkedQueue<>();
|
||||
private final List<AppInfo> showList = new ArrayList<>();
|
||||
private final List<String> denyList = new ArrayList<>();
|
||||
|
||||
private final SwitchBar.OnCheckedChangeListener switchBarOnCheckedChangeListener = new SwitchBar.OnCheckedChangeListener() {
|
||||
@Override
|
||||
|
|
@ -161,6 +162,11 @@ public class ScopeAdapter extends RecyclerView.Adapter<ScopeAdapter.ViewHolder>
|
|||
if (checkedList.contains(app)) {
|
||||
return false;
|
||||
}
|
||||
if (preferences.getBoolean("filter_denylist", false)) {
|
||||
if (denyList.contains(info.packageName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (preferences.getBoolean("filter_modules", true)) {
|
||||
if (info.applicationInfo.metaData != null && info.applicationInfo.metaData.containsKey("xposedminversion")) {
|
||||
return true;
|
||||
|
|
@ -248,6 +254,9 @@ public class ScopeAdapter extends RecyclerView.Adapter<ScopeAdapter.ViewHolder>
|
|||
} else if (itemId == R.id.item_filter_modules) {
|
||||
item.setChecked(!item.isChecked());
|
||||
preferences.edit().putBoolean("filter_modules", item.isChecked()).apply();
|
||||
} else if (itemId == R.id.item_filter_denylist) {
|
||||
item.setChecked(!item.isChecked());
|
||||
preferences.edit().putBoolean("filter_denylist", item.isChecked()).apply();
|
||||
} else if (itemId == R.id.menu_launch) {
|
||||
Intent launchIntent = AppHelper.getSettingsIntent(module.packageName, module.userId);
|
||||
if (launchIntent != null) {
|
||||
|
|
@ -320,6 +329,7 @@ public class ScopeAdapter extends RecyclerView.Adapter<ScopeAdapter.ViewHolder>
|
|||
menu.findItem(R.id.item_filter_system).setChecked(preferences.getBoolean("filter_system_apps", true));
|
||||
menu.findItem(R.id.item_filter_games).setChecked(preferences.getBoolean("filter_games", true));
|
||||
menu.findItem(R.id.item_filter_modules).setChecked(preferences.getBoolean("filter_modules", true));
|
||||
menu.findItem(R.id.item_filter_denylist).setChecked(preferences.getBoolean("filter_denylist", false));
|
||||
switch (preferences.getInt("list_sort", 0)) {
|
||||
case 7:
|
||||
menu.findItem(R.id.item_sort_by_update_time).setChecked(true);
|
||||
|
|
@ -354,8 +364,9 @@ public class ScopeAdapter extends RecyclerView.Adapter<ScopeAdapter.ViewHolder>
|
|||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||
holder.root.setAlpha(enabled ? 1.0f : .5f);
|
||||
AppInfo appInfo = showList.get(position);
|
||||
boolean deny = denyList.contains(appInfo.packageName);
|
||||
holder.root.setAlpha(!deny && enabled ? 1.0f : .5f);
|
||||
boolean android = appInfo.packageName.equals("android");
|
||||
CharSequence appName;
|
||||
int userId = appInfo.applicationInfo.uid / 100000;
|
||||
|
|
@ -403,6 +414,21 @@ public class ScopeAdapter extends RecyclerView.Adapter<ScopeAdapter.ViewHolder>
|
|||
} else {
|
||||
holder.hint.setVisibility(View.GONE);
|
||||
}
|
||||
if (deny) {
|
||||
if (sb.length() != 0) sb.append("\n");
|
||||
String denylist = activity.getString(R.string.deny_list_info);
|
||||
sb.append(denylist);
|
||||
final ForegroundColorSpan foregroundColorSpan = new ForegroundColorSpan(ResourceUtils.resolveColor(activity.getTheme(), rikka.material.R.attr.colorWarning));
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
final TypefaceSpan typefaceSpan = new TypefaceSpan(Typeface.create("sans-serif-medium", Typeface.NORMAL));
|
||||
sb.setSpan(typefaceSpan, sb.length() - denylist.length(), sb.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
|
||||
} else {
|
||||
final StyleSpan styleSpan = new StyleSpan(Typeface.BOLD);
|
||||
sb.setSpan(styleSpan, sb.length() - denylist.length(), sb.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
|
||||
}
|
||||
sb.setSpan(foregroundColorSpan, sb.length() - denylist.length(), sb.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
|
||||
}
|
||||
holder.hint.setText(sb);
|
||||
|
||||
holder.itemView.setOnCreateContextMenuListener((menu, v, menuInfo) -> {
|
||||
activity.getMenuInflater().inflate(R.menu.menu_app_item, menu);
|
||||
|
|
@ -470,6 +496,8 @@ public class ScopeAdapter extends RecyclerView.Adapter<ScopeAdapter.ViewHolder>
|
|||
List<PackageInfo> appList = AppHelper.getAppList(force);
|
||||
checkedList.clear();
|
||||
recommendedList.clear();
|
||||
denyList.clear();
|
||||
denyList.addAll(AppHelper.getDenyList(force));
|
||||
var tmpList = new ArrayList<AppInfo>();
|
||||
checkedList.addAll(ConfigManager.getModuleScope(module.packageName));
|
||||
HashSet<ApplicationWithEquals> installedList = new HashSet<>();
|
||||
|
|
@ -548,6 +576,9 @@ public class ScopeAdapter extends RecyclerView.Adapter<ScopeAdapter.ViewHolder>
|
|||
Snackbar.make(fragment.binding.snackbar, R.string.reboot_required, Snackbar.LENGTH_SHORT)
|
||||
.setAction(R.string.reboot, v -> ConfigManager.reboot(false))
|
||||
.show();
|
||||
} else if (denyList.contains(appInfo.packageName)) {
|
||||
Snackbar.make(fragment.binding.snackbar, activity.getString(R.string.deny_list, appInfo.label), Snackbar.LENGTH_SHORT)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -58,6 +58,12 @@
|
|||
android:checkable="true"
|
||||
android:checked="true"
|
||||
android:title="@string/menu_show_system_apps" />
|
||||
|
||||
<item
|
||||
android:id="@+id/item_filter_denylist"
|
||||
android:checkable="true"
|
||||
android:checked="true"
|
||||
android:title="@string/menu_show_denylist" />
|
||||
</menu>
|
||||
</item>
|
||||
|
||||
|
|
|
|||
|
|
@ -124,6 +124,7 @@
|
|||
<string name="no_scope_selected">You did not select any app. Continue?</string>
|
||||
<string name="menu_show_games">Games</string>
|
||||
<string name="menu_show_modules">Modules</string>
|
||||
<string name="menu_show_denylist">Denylist</string>
|
||||
<string name="failed_to_save_scope_list">Failed save scope list</string>
|
||||
<string name="app_description">%1$s\nVersion %2$s</string>
|
||||
<string name="use_recommended">Recommended</string>
|
||||
|
|
@ -142,6 +143,8 @@
|
|||
<string name="reboot_required">Reboot is required for apply changes</string>
|
||||
<string name="reboot">Reboot</string>
|
||||
<string name="menu_hide">Hide</string>
|
||||
<string name="deny_list">%s is on denylist. It may not take effect.</string>
|
||||
<string name="deny_list_info">On denylist</string>
|
||||
|
||||
<!-- ModulesActivity and AppListActivity -->
|
||||
<string name="modules_other_app">View in other app</string>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
/build
|
||||
/release
|
||||
/src/main/cpp/main/src/config.cpp
|
||||
/src/main/cpp/main/api/config.cpp
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ plugins {
|
|||
}
|
||||
|
||||
val moduleName = "LSPosed"
|
||||
val moduleId = "riru_lsposed"
|
||||
val moduleBaseId = "lsposed"
|
||||
val authors = "LSPosed Developers"
|
||||
|
||||
val riruModuleId = "lsposed"
|
||||
|
|
@ -62,30 +62,13 @@ val androidCompileNdkVersion: String by rootProject.extra
|
|||
val androidSourceCompatibility: JavaVersion by rootProject.extra
|
||||
val androidTargetCompatibility: JavaVersion by rootProject.extra
|
||||
|
||||
dependencies {
|
||||
implementation("dev.rikka.ndk:riru:26.0.0")
|
||||
implementation("dev.rikka.ndk.thirdparty:cxx:1.1.0")
|
||||
implementation("io.github.vvb2060.ndk:dobby:1.2")
|
||||
implementation("com.android.tools.build:apksig:$agpVersion")
|
||||
implementation("org.apache.commons:commons-lang3:3.12.0")
|
||||
implementation("de.upb.cs.swt:axml:2.1.1")
|
||||
compileOnly("androidx.annotation:annotation:1.2.0")
|
||||
compileOnly(project(":hiddenapi-stubs"))
|
||||
implementation(project(":hiddenapi-bridge"))
|
||||
implementation(project(":manager-service"))
|
||||
debugImplementation(files(File(project.buildDir, "tmp/debugR.jar")) {
|
||||
builtBy("generateAppDebugRFile")
|
||||
})
|
||||
releaseImplementation(files(File(project.buildDir, "tmp/releaseR.jar")) {
|
||||
builtBy("generateAppReleaseRFile")
|
||||
})
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk = androidCompileSdkVersion
|
||||
ndkVersion = androidCompileNdkVersion
|
||||
buildToolsVersion = androidBuildToolsVersion
|
||||
|
||||
flavorDimensions += "api"
|
||||
|
||||
buildFeatures {
|
||||
prefab = true
|
||||
}
|
||||
|
|
@ -100,9 +83,9 @@ android {
|
|||
|
||||
externalNativeBuild {
|
||||
ndkBuild {
|
||||
arguments += "RIRU_MODULE_API_VERSION=$moduleMaxRiruApiVersion"
|
||||
arguments += "MODULE_NAME=$riruModuleId"
|
||||
arguments += "INJECTED_AID=$injectedPackageUid"
|
||||
arguments += "VERSION_CODE=$verCode"
|
||||
arguments += "VERSION_NAME=$verName"
|
||||
arguments += "-j${Runtime.getRuntime().availableProcessors()}"
|
||||
}
|
||||
}
|
||||
|
|
@ -139,6 +122,59 @@ android {
|
|||
sourceCompatibility(androidSourceCompatibility)
|
||||
}
|
||||
|
||||
productFlavors {
|
||||
all {
|
||||
externalNativeBuild {
|
||||
ndkBuild {
|
||||
arguments += "MODULE_NAME=${name.toLowerCase()}_$moduleBaseId"
|
||||
arguments += "API=${name.toLowerCase()}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
create("Riru") {
|
||||
dimension = "api"
|
||||
externalNativeBuild {
|
||||
ndkBuild {
|
||||
arguments += "API_VERSION=$moduleMaxRiruApiVersion"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
create("Zygisk") {
|
||||
dimension = "api"
|
||||
externalNativeBuild {
|
||||
ndkBuild {
|
||||
arguments += "API_VERSION=1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
dependencies {
|
||||
// keep this dep since it affects ccache
|
||||
implementation("dev.rikka.ndk:riru:26.0.0")
|
||||
implementation("dev.rikka.ndk.thirdparty:cxx:1.1.0")
|
||||
implementation("io.github.vvb2060.ndk:dobby:1.2")
|
||||
implementation("com.android.tools.build:apksig:$agpVersion")
|
||||
implementation("org.apache.commons:commons-lang3:3.12.0")
|
||||
implementation("de.upb.cs.swt:axml:2.1.1")
|
||||
compileOnly("androidx.annotation:annotation:1.2.0")
|
||||
compileOnly(project(":hiddenapi-stubs"))
|
||||
implementation(project(":hiddenapi-bridge"))
|
||||
implementation(project(":manager-service"))
|
||||
android.applicationVariants.all {
|
||||
"${name}Implementation"(files(File(project.buildDir, "tmp/${name}R.jar")) {
|
||||
builtBy("generateApp${name}RFile")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
val zipAll = task("zipAll", Task::class) {
|
||||
|
||||
}
|
||||
|
||||
androidComponents.onVariants { v ->
|
||||
|
|
@ -147,19 +183,23 @@ androidComponents.onVariants { v ->
|
|||
else (v as AnalyticsEnabledApplicationVariant).delegate as ApplicationVariantImpl
|
||||
val variantCapped = variant.name.capitalize()
|
||||
val variantLowered = variant.name.toLowerCase()
|
||||
val zipFileName = "$moduleName-v$verName-$verCode-$variantLowered.zip"
|
||||
val buildTypeCapped = variant.buildType!!.capitalize()
|
||||
val buildTypeLowered = variant.buildType!!.toLowerCase()
|
||||
val flavorCapped = variant.flavorName!!.capitalize()
|
||||
val flavorLowered = variant.flavorName!!.toLowerCase()
|
||||
|
||||
val magiskDir = "$buildDir/magisk/$variantLowered"
|
||||
|
||||
task("generateApp${variantCapped}RFile", Jar::class) {
|
||||
dependsOn(":app:process${variantCapped}Resources")
|
||||
dependsOn(":app:process${buildTypeCapped}Resources")
|
||||
doLast {
|
||||
val rFile = JarFile(
|
||||
File(
|
||||
project(":app").buildDir,
|
||||
"intermediates/compile_and_runtime_not_namespaced_r_class_jar/${variantLowered}/R.jar"
|
||||
"intermediates/compile_and_runtime_not_namespaced_r_class_jar/${buildTypeLowered}/R.jar"
|
||||
)
|
||||
)
|
||||
ZipOutputStream(FileOutputStream(File(project.buildDir, "tmp/${variantLowered}R.jar"))).use {
|
||||
ZipOutputStream(FileOutputStream(File(project.buildDir, "tmp/${variantCapped}R.jar"))).use {
|
||||
for (entry in rFile.entries()) {
|
||||
if (entry.name.startsWith("org/lsposed/manager")) {
|
||||
it.putNextEntry(entry)
|
||||
|
|
@ -176,10 +216,10 @@ androidComponents.onVariants { v ->
|
|||
val outSrcDir = file("$buildDir/generated/source/signInfo/${variantLowered}")
|
||||
val outSrc = file("$outSrcDir/org/lsposed/lspd/util/SignInfo.java")
|
||||
val signInfoTask = tasks.register("generate${variantCapped}SignInfo") {
|
||||
dependsOn(":app:validateSigning${variantCapped}")
|
||||
dependsOn(":app:validateSigning${buildTypeCapped}")
|
||||
outputs.file(outSrc)
|
||||
doLast {
|
||||
val sign = app.buildTypes.named(variantLowered).get().signingConfig
|
||||
val sign = app.buildTypes.named(buildTypeLowered).get().signingConfig
|
||||
outSrc.parentFile.mkdirs()
|
||||
val certificateInfo = KeystoreHelper.getCertificateInfo(
|
||||
sign?.storeType,
|
||||
|
|
@ -202,13 +242,16 @@ androidComponents.onVariants { v ->
|
|||
variant.variantData.registerJavaGeneratingTask(signInfoTask, arrayListOf(outSrcDir))
|
||||
}
|
||||
|
||||
val moduleId = "${flavorLowered}_$moduleBaseId"
|
||||
val zipFileName = "$moduleName-${flavorLowered}-v$verName-$verCode-$buildTypeLowered.zip"
|
||||
|
||||
val prepareMagiskFilesTask = task("prepareMagiskFiles$variantCapped", Sync::class) {
|
||||
dependsOn("assemble$variantCapped")
|
||||
dependsOn(":app:assemble$variantCapped")
|
||||
dependsOn(":app:assemble$buildTypeCapped")
|
||||
into(magiskDir)
|
||||
from("${rootProject.projectDir}/README.md")
|
||||
from("$projectDir/magisk_module") {
|
||||
exclude("riru.sh", "module.prop")
|
||||
exclude("riru.sh", "module.prop", "customize.sh")
|
||||
}
|
||||
from("$projectDir/magisk_module") {
|
||||
include("module.prop")
|
||||
|
|
@ -217,32 +260,45 @@ androidComponents.onVariants { v ->
|
|||
"versionName" to "v$verName",
|
||||
"versionCode" to verCode,
|
||||
"authorList" to authors,
|
||||
"minRiruVersionName" to moduleMinRiruVersionName
|
||||
"requirement" to when (flavorLowered) {
|
||||
"riru" -> "Requires Riru $moduleMinRiruVersionName or above installed"
|
||||
"zygisk" -> "Requires Magisk 24.0+ and Zygisk enabled"
|
||||
else -> "No further requirements"
|
||||
},
|
||||
"api" to flavorCapped
|
||||
)
|
||||
filter<FixCrLfFilter>("eol" to FixCrLfFilter.CrLf.newInstance("lf"))
|
||||
}
|
||||
from("${projectDir}/magisk_module") {
|
||||
include("riru.sh")
|
||||
val tokens = mapOf(
|
||||
"RIRU_MODULE_LIB_NAME" to "lspd",
|
||||
"RIRU_MODULE_API_VERSION" to moduleMaxRiruApiVersion.toString(),
|
||||
"RIRU_MODULE_MIN_API_VERSION" to moduleMinRiruApiVersion.toString(),
|
||||
"RIRU_MODULE_MIN_RIRU_VERSION_NAME" to moduleMinRiruVersionName,
|
||||
"RIRU_MODULE_DEBUG" to if (variantLowered == "debug") "true" else "false",
|
||||
)
|
||||
from("$projectDir/magisk_module") {
|
||||
include("customize.sh")
|
||||
val tokens = mapOf("FLAVOR" to flavorLowered)
|
||||
filter<ReplaceTokens>("tokens" to tokens)
|
||||
filter<FixCrLfFilter>("eol" to FixCrLfFilter.CrLf.newInstance("lf"))
|
||||
}
|
||||
from("${project(":app").buildDir}/outputs/apk/${variantLowered}") {
|
||||
if (flavorLowered == "riru") {
|
||||
from("${projectDir}/magisk_module") {
|
||||
include("riru.sh")
|
||||
val tokens = mapOf(
|
||||
"RIRU_MODULE_LIB_NAME" to "lspd",
|
||||
"RIRU_MODULE_API_VERSION" to moduleMaxRiruApiVersion.toString(),
|
||||
"RIRU_MODULE_MIN_API_VERSION" to moduleMinRiruApiVersion.toString(),
|
||||
"RIRU_MODULE_MIN_RIRU_VERSION_NAME" to moduleMinRiruVersionName,
|
||||
"RIRU_MODULE_DEBUG" to if (buildTypeLowered == "debug") "true" else "false",
|
||||
)
|
||||
filter<ReplaceTokens>("tokens" to tokens)
|
||||
filter<FixCrLfFilter>("eol" to FixCrLfFilter.CrLf.newInstance("lf"))
|
||||
}
|
||||
}
|
||||
from("${project(":app").buildDir}/outputs/apk/${buildTypeLowered}") {
|
||||
include("*.apk")
|
||||
rename(".*\\.apk", "manager.apk")
|
||||
}
|
||||
into("lib") {
|
||||
from("${buildDir}/intermediates/stripped_native_libs/$variantLowered/out/lib")
|
||||
from("${buildDir}/intermediates/stripped_native_libs/$variantCapped/out/lib")
|
||||
}
|
||||
val dexOutPath = if (variantLowered == "release")
|
||||
"$buildDir/intermediates/dex/$variantLowered/minify${variantCapped}WithR8" else
|
||||
"$buildDir/intermediates/dex/$variantLowered/mergeDex$variantCapped"
|
||||
val dexOutPath = if (buildTypeLowered == "release")
|
||||
"$buildDir/intermediates/dex/$variantCapped/minify${variantCapped}WithR8" else
|
||||
"$buildDir/intermediates/dex/$variantCapped/mergeDex$variantCapped"
|
||||
into("framework") {
|
||||
from(dexOutPath)
|
||||
rename("classes.dex", "lspd.dex")
|
||||
|
|
@ -266,6 +322,8 @@ androidComponents.onVariants { v ->
|
|||
from(magiskDir)
|
||||
}
|
||||
|
||||
zipAll.dependsOn(zipTask)
|
||||
|
||||
val adb: String = androidComponents.sdkComponents.adb.get().asFile.absolutePath
|
||||
val pushTask = task("push${variantCapped}", Exec::class) {
|
||||
dependsOn(zipTask)
|
||||
|
|
@ -313,7 +371,7 @@ val reRunLspd = task("reRunLspd", Exec::class) {
|
|||
dependsOn(pushLspd)
|
||||
dependsOn(pushLspdNative)
|
||||
dependsOn(killLspd)
|
||||
commandLine(adb, "shell", "su", "-c", "sh /data/adb/modules/riru_lsposed/service.sh&")
|
||||
commandLine(adb, "shell", "su", "-c", "sh /data/adb/modules/*_lsposed/service.sh&")
|
||||
isIgnoreExitValue = true
|
||||
}
|
||||
val tmpApk = "/data/local/tmp/lsp.apk"
|
||||
|
|
@ -335,13 +393,3 @@ task("reRunApp", Exec::class) {
|
|||
isIgnoreExitValue = true
|
||||
finalizedBy(reRunLspd)
|
||||
}
|
||||
|
||||
val generateVersion = task("generateVersion", Copy::class) {
|
||||
inputs.property("VERSION_CODE", verCode)
|
||||
inputs.property("VERSION_NAME", verName)
|
||||
from("${projectDir}/src/main/cpp/main/template")
|
||||
include("config.cpp")
|
||||
expand("VERSION_CODE" to verCode, "VERSION_NAME" to verName)
|
||||
into("${projectDir}/src/main/cpp/main/src")
|
||||
}
|
||||
tasks.getByName("preBuild").dependsOn(generateVersion)
|
||||
|
|
|
|||
|
|
@ -21,6 +21,20 @@
|
|||
# shellcheck disable=SC2034
|
||||
SKIPUNZIP=1
|
||||
|
||||
FLAVOR=@FLAVOR@
|
||||
|
||||
enforce_install_from_magisk_app() {
|
||||
if $BOOTMODE; then
|
||||
ui_print "- Installing from Magisk app"
|
||||
else
|
||||
ui_print "*********************************************************"
|
||||
ui_print "! Install from recovery is NOT supported"
|
||||
ui_print "! Some recovery has broken implementations, install with such recovery will finally cause Riru or Riru modules not working"
|
||||
ui_print "! Please install from Magisk app"
|
||||
abort "*********************************************************"
|
||||
fi
|
||||
}
|
||||
|
||||
VERSION=$(grep_prop version "${TMPDIR}/module.prop")
|
||||
ui_print "- LSPosed version ${VERSION}"
|
||||
|
||||
|
|
@ -44,12 +58,14 @@ check_android_version
|
|||
check_magisk_version
|
||||
check_incompatible_module
|
||||
|
||||
# Extract riru.sh
|
||||
extract "$ZIPFILE" 'riru.sh' "$TMPDIR"
|
||||
. "$TMPDIR/riru.sh"
|
||||
if [ "$FLAVOR" == "riru" ]; then
|
||||
# Extract riru.sh
|
||||
extract "$ZIPFILE" 'riru.sh' "$TMPDIR"
|
||||
. "$TMPDIR/riru.sh"
|
||||
# Functions from riru.sh
|
||||
check_riru_version
|
||||
fi
|
||||
|
||||
# Functions from riru.sh
|
||||
check_riru_version
|
||||
enforce_install_from_magisk_app
|
||||
|
||||
# Check architecture
|
||||
|
|
@ -73,48 +89,63 @@ extract "$ZIPFILE" 'lspd' "$MODPATH"
|
|||
rm -f /data/adb/lspd/manager.apk
|
||||
extract "$ZIPFILE" 'manager.apk' '/data/adb/lspd'
|
||||
|
||||
mkdir "$MODPATH/riru"
|
||||
mkdir "$MODPATH/riru/lib"
|
||||
mkdir "$MODPATH/riru/lib64"
|
||||
|
||||
if [ "$ARCH" = "arm" ] || [ "$ARCH" = "arm64" ]; then
|
||||
ui_print "- Extracting arm libraries"
|
||||
extract "$ZIPFILE" "lib/armeabi-v7a/lib$RIRU_MODULE_LIB_NAME.so" "$MODPATH/riru/lib" true
|
||||
|
||||
if [ "$IS64BIT" = true ]; then
|
||||
ui_print "- Extracting arm64 libraries"
|
||||
extract "$ZIPFILE" "lib/arm64-v8a/lib$RIRU_MODULE_LIB_NAME.so" "$MODPATH/riru/lib64" true
|
||||
extract "$ZIPFILE" 'lib/arm64-v8a/libdaemon.so' "$MODPATH" true
|
||||
else
|
||||
extract "$ZIPFILE" 'lib/armeabi-v7a/libdaemon.so' "$MODPATH" true
|
||||
fi
|
||||
ui_print "- Extracting daemon libraries"
|
||||
if [ "$ARCH" = "arm" ] ; then
|
||||
extract "$ZIPFILE" 'lib/armeabi-v7a/libdaemon.so' "$MODPATH" true
|
||||
elif [ "$ARCH" = "arm64" ]; then
|
||||
extract "$ZIPFILE" 'lib/arm64-v8a/libdaemon.so' "$MODPATH" true
|
||||
elif [ "$ARCH" = "x86" ]; then
|
||||
extract "$ZIPFILE" 'lib/x86/libdaemon.so' "$MODPATH" true
|
||||
elif [ "$ARCH" = "x64" ]; then
|
||||
extract "$ZIPFILE" 'lib/x86_64/libdaemon.so' "$MODPATH" true
|
||||
fi
|
||||
if [ "$FLAVOR" == "zygisk" ]; then
|
||||
mkdir -p "$MODPATH/zygisk"
|
||||
extract "$ZIPFILE" "lib/armeabi-v7a/liblspd.so" "$MODPATH/zygisk" true
|
||||
mv "$MODPATH/zygisk/liblspd.so" "$MODPATH/zygisk/armeabi-v7a.so"
|
||||
extract "$ZIPFILE" "lib/arm64-v8a/liblspd.so" "$MODPATH/zygisk" true
|
||||
mv "$MODPATH/zygisk/liblspd.so" "$MODPATH/zygisk/arm64-v8a.so"
|
||||
extract "$ZIPFILE" "lib/x86_64/liblspd.so" "$MODPATH/zygisk" true
|
||||
mv "$MODPATH/zygisk/liblspd.so" "$MODPATH/zygisk/x86_64.so"
|
||||
extract "$ZIPFILE" "lib/x86/liblspd.so" "$MODPATH/zygisk" true
|
||||
mv "$MODPATH/zygisk/liblspd.so" "$MODPATH/zygisk/x86.so"
|
||||
elif [ "$FLAVOR" == "riru" ]; then
|
||||
mkdir "$MODPATH/riru"
|
||||
mkdir "$MODPATH/riru/lib"
|
||||
mkdir "$MODPATH/riru/lib64"
|
||||
if [ "$ARCH" = "arm" ] || [ "$ARCH" = "arm64" ]; then
|
||||
ui_print "- Extracting arm libraries"
|
||||
extract "$ZIPFILE" "lib/armeabi-v7a/lib$RIRU_MODULE_LIB_NAME.so" "$MODPATH/riru/lib" true
|
||||
|
||||
if [ "$ARCH" = "x86" ] || [ "$ARCH" = "x64" ]; then
|
||||
ui_print "- Extracting x86 libraries"
|
||||
extract "$ZIPFILE" "lib/x86/lib$RIRU_MODULE_LIB_NAME.so" "$MODPATH/riru/lib" true
|
||||
|
||||
if [ "$IS64BIT" = true ]; then
|
||||
ui_print "- Extracting x64 libraries"
|
||||
extract "$ZIPFILE" "lib/x86_64/lib$RIRU_MODULE_LIB_NAME.so" "$MODPATH/riru/lib64" true
|
||||
extract "$ZIPFILE" 'lib/x86_64/libdaemon.so' "$MODPATH" true
|
||||
else
|
||||
extract "$ZIPFILE" 'lib/x86/libdaemon.so' "$MODPATH" true
|
||||
if [ "$IS64BIT" = true ]; then
|
||||
ui_print "- Extracting arm64 libraries"
|
||||
extract "$ZIPFILE" "lib/arm64-v8a/lib$RIRU_MODULE_LIB_NAME.so" "$MODPATH/riru/lib64" true
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$RIRU_MODULE_DEBUG" = true ]; then
|
||||
mv "$MODPATH/riru" "$MODPATH/system"
|
||||
mv "$MODPATH/system/lib/lib$RIRU_MODULE_LIB_NAME.so" "$MODPATH/system/lib/libriru_$RIRU_MODULE_LIB_NAME.so"
|
||||
mv "$MODPATH/system/lib64/lib$RIRU_MODULE_LIB_NAME.so" "$MODPATH/system/lib64/libriru_$RIRU_MODULE_LIB_NAME.so"
|
||||
mv "$MODPATH/framework" "$MODPATH/system/framework"
|
||||
if [ "$RIRU_API" -ge 26 ]; then
|
||||
mkdir -p "$MODPATH/riru/lib"
|
||||
mkdir -p "$MODPATH/riru/lib64"
|
||||
touch "$MODPATH/riru/lib/libriru_$RIRU_MODULE_LIB_NAME"
|
||||
touch "$MODPATH/riru/lib64/libriru_$RIRU_MODULE_LIB_NAME"
|
||||
else
|
||||
mkdir -p "/data/adb/riru/modules/$RIRU_MODULE_LIB_NAME"
|
||||
if [ "$ARCH" = "x86" ] || [ "$ARCH" = "x64" ]; then
|
||||
ui_print "- Extracting x86 libraries"
|
||||
extract "$ZIPFILE" "lib/x86/lib$RIRU_MODULE_LIB_NAME.so" "$MODPATH/riru/lib" true
|
||||
|
||||
if [ "$IS64BIT" = true ]; then
|
||||
ui_print "- Extracting x64 libraries"
|
||||
extract "$ZIPFILE" "lib/x86_64/lib$RIRU_MODULE_LIB_NAME.so" "$MODPATH/riru/lib64" true
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$RIRU_MODULE_DEBUG" = true ]; then
|
||||
mv "$MODPATH/riru" "$MODPATH/system"
|
||||
mv "$MODPATH/system/lib/lib$RIRU_MODULE_LIB_NAME.so" "$MODPATH/system/lib/libriru_$RIRU_MODULE_LIB_NAME.so"
|
||||
mv "$MODPATH/system/lib64/lib$RIRU_MODULE_LIB_NAME.so" "$MODPATH/system/lib64/libriru_$RIRU_MODULE_LIB_NAME.so"
|
||||
mv "$MODPATH/framework" "$MODPATH/system/framework"
|
||||
if [ "$RIRU_API" -ge 26 ]; then
|
||||
mkdir -p "$MODPATH/riru/lib"
|
||||
mkdir -p "$MODPATH/riru/lib64"
|
||||
touch "$MODPATH/riru/lib/libriru_$RIRU_MODULE_LIB_NAME"
|
||||
touch "$MODPATH/riru/lib64/libriru_$RIRU_MODULE_LIB_NAME"
|
||||
else
|
||||
mkdir -p "/data/adb/riru/modules/$RIRU_MODULE_LIB_NAME"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
id=${moduleId}
|
||||
name=Riru - LSPosed
|
||||
name=${api} - LSPosed
|
||||
version=${versionName} (${versionCode})
|
||||
versionCode=${versionCode}
|
||||
author=${authorList}
|
||||
description=Another enhanced implementation of Xposed Framework. Supports Android 8.1 ~ 12. Requires Riru ${minRiruVersionName} or above installed.
|
||||
description=Another enhanced implementation of Xposed Framework. Supports Android 8.1 ~ 12. ${requirement}.
|
||||
|
|
|
|||
|
|
@ -1 +1,4 @@
|
|||
allow system_server system_server process execmem
|
||||
|
||||
# Used to load dex and should be removed after companion is ready
|
||||
allow zygote adb_data_file dir search
|
||||
|
|
|
|||
|
|
@ -7,10 +7,23 @@ include $(CLEAR_VARS)
|
|||
LOCAL_MODULE := lspd
|
||||
LOCAL_C_INCLUDES := $(LOCAL_PATH)/include $(LOCAL_PATH)/src
|
||||
FILE_LIST := $(filter %.cpp, $(call walk, $(LOCAL_PATH)/src))
|
||||
LOCAL_SRC_FILES := $(FILE_LIST:$(LOCAL_PATH)/%=%)
|
||||
LOCAL_STATIC_LIBRARIES := cxx yahfa riru dobby dex_builder
|
||||
LOCAL_CFLAGS := -DRIRU_MODULE -DRIRU_MODULE_API_VERSION=${RIRU_MODULE_API_VERSION}
|
||||
LOCAL_CFLAGS += -DMODULE_NAME=${MODULE_NAME}
|
||||
LOCAL_SRC_FILES := $(FILE_LIST:$(LOCAL_PATH)/%=%) api/config.cpp
|
||||
LOCAL_STATIC_LIBRARIES := cxx riru yahfa dobby dex_builder
|
||||
ifeq ($(API), riru)
|
||||
LOCAL_SRC_FILES += api/riru_main.cpp
|
||||
else ifeq ($(API), zygisk)
|
||||
LOCAL_SRC_FILES += api/zygisk_main.cpp
|
||||
endif
|
||||
LOCAL_CFLAGS += -DINJECTED_AID=${INJECTED_AID}
|
||||
LOCAL_LDLIBS := -llog
|
||||
include $(BUILD_SHARED_LIBRARY)
|
||||
|
||||
$(LOCAL_PATH)/api/config.cpp : FORCE
|
||||
$(file > $@,#include "config.h")
|
||||
$(file >> $@,namespace lspd {)
|
||||
$(file >> $@,const int versionCode = ${VERSION_CODE};)
|
||||
$(file >> $@,const int apiVersion = ${API_VERSION};)
|
||||
$(file >> $@,const char* const versionName = "${VERSION_NAME}";)
|
||||
$(file >> $@,const char* const moduleName = "${MODULE_NAME}";)
|
||||
$(file >> $@,})
|
||||
FORCE: ;
|
||||
|
|
|
|||
|
|
@ -25,59 +25,51 @@
|
|||
#include "logging.h"
|
||||
#include "config.h"
|
||||
#include "context.h"
|
||||
|
||||
#define RIRU_MODULE
|
||||
#include <riru.h>
|
||||
#include "symbol_cache.h"
|
||||
|
||||
namespace lspd {
|
||||
int *allowUnload = nullptr;
|
||||
|
||||
static constexpr uid_t kAidInjected = INJECTED_AID;
|
||||
static constexpr uid_t kAidInet = 3003;
|
||||
|
||||
namespace {
|
||||
std::string magiskPath;
|
||||
|
||||
jstring nice_name = nullptr;
|
||||
jstring app_data_dir = nullptr;
|
||||
|
||||
void onModuleLoaded() {
|
||||
LOGI("onModuleLoaded: welcome to LSPosed!");
|
||||
LOGI("onModuleLoaded: version v%s (%d)", versionName, versionCode);
|
||||
Context::GetInstance()->Init();
|
||||
if constexpr (isDebug) {
|
||||
Context::GetInstance()->PreLoadDex("/system/" + kDexPath);
|
||||
} else {
|
||||
Context::GetInstance()->PreLoadDex(magiskPath + '/' + kDexPath);
|
||||
}
|
||||
InitSymbolCache();
|
||||
}
|
||||
|
||||
void nativeForkAndSpecializePre(JNIEnv *env, jclass, jint *_uid, jint *,
|
||||
jintArray *gids, jint *,
|
||||
jobjectArray *, jint *,
|
||||
jstring *, jstring *nice_name,
|
||||
jstring *, jstring *_nice_name,
|
||||
jintArray *, jintArray *,
|
||||
jboolean *start_child_zygote, jstring *,
|
||||
jstring *app_data_dir, jboolean *,
|
||||
jstring *_app_data_dir, jboolean *,
|
||||
jobjectArray *,
|
||||
jobjectArray *,
|
||||
jboolean *,
|
||||
jboolean *) {
|
||||
if (*_uid == kAidInjected) {
|
||||
int array_size = *gids ? env->GetArrayLength(*gids) : 0;
|
||||
auto region = std::make_unique<jint[]>(array_size + 1);
|
||||
auto *new_gids = env->NewIntArray(array_size + 1);
|
||||
if (*gids) env->GetIntArrayRegion(*gids, 0, array_size, region.get());
|
||||
region.get()[array_size] = kAidInet;
|
||||
env->SetIntArrayRegion(new_gids, 0, array_size + 1, region.get());
|
||||
if (*gids) env->SetIntArrayRegion(*gids, 0, 1, region.get() + array_size);
|
||||
*gids = new_gids;
|
||||
}
|
||||
Context::GetInstance()->OnNativeForkAndSpecializePre(env, *_uid,
|
||||
*nice_name,
|
||||
nice_name = *_nice_name;
|
||||
app_data_dir = *_app_data_dir;
|
||||
Context::GetInstance()->OnNativeForkAndSpecializePre(env, *_uid, *gids,
|
||||
nice_name,
|
||||
*start_child_zygote,
|
||||
*app_data_dir);
|
||||
app_data_dir);
|
||||
}
|
||||
|
||||
void nativeForkAndSpecializePost(JNIEnv *env, jclass, jint res) {
|
||||
if (res == 0)
|
||||
Context::GetInstance()->OnNativeForkAndSpecializePost(env);
|
||||
Context::GetInstance()->OnNativeForkAndSpecializePost(env, nice_name, app_data_dir);
|
||||
}
|
||||
|
||||
void nativeForkSystemServerPre(JNIEnv *env, jclass, uid_t *, gid_t *,
|
||||
|
|
@ -88,43 +80,36 @@ namespace lspd {
|
|||
}
|
||||
|
||||
void nativeForkSystemServerPost(JNIEnv *env, jclass, jint res) {
|
||||
Context::GetInstance()->OnNativeForkSystemServerPost(env, res);
|
||||
if (res == 0)
|
||||
Context::GetInstance()->OnNativeForkSystemServerPost(env);
|
||||
}
|
||||
|
||||
/* method added in Android Q */
|
||||
void specializeAppProcessPre(JNIEnv *env, jclass, jint *_uid, jint *,
|
||||
jintArray *gids, jint *,
|
||||
jobjectArray *, jint *,
|
||||
jstring *, jstring *nice_name,
|
||||
jstring *, jstring *_nice_name,
|
||||
jboolean *start_child_zygote, jstring *,
|
||||
jstring *app_data_dir, jboolean *,
|
||||
jstring *_app_data_dir, jboolean *,
|
||||
jobjectArray *,
|
||||
jobjectArray *,
|
||||
jboolean *,
|
||||
jboolean *) {
|
||||
if (*_uid == kAidInjected) {
|
||||
int array_size = *gids ? env->GetArrayLength(*gids) : 0;
|
||||
auto region = std::make_unique<jint[]>(array_size + 1);
|
||||
auto *new_gids = env->NewIntArray(array_size + 1);
|
||||
if (*gids) env->GetIntArrayRegion(*gids, 0, array_size, region.get());
|
||||
region.get()[array_size] = kAidInet;
|
||||
env->SetIntArrayRegion(new_gids, 0, array_size + 1, region.get());
|
||||
if (*gids) env->SetIntArrayRegion(*gids, 0, 1, region.get() + array_size);
|
||||
*gids = new_gids;
|
||||
}
|
||||
Context::GetInstance()->OnNativeForkAndSpecializePre(env, *_uid,
|
||||
*nice_name,
|
||||
nice_name = *_nice_name;
|
||||
app_data_dir = *_app_data_dir;
|
||||
Context::GetInstance()->OnNativeForkAndSpecializePre(env, *_uid, *gids,
|
||||
nice_name,
|
||||
*start_child_zygote,
|
||||
*app_data_dir);
|
||||
app_data_dir);
|
||||
}
|
||||
|
||||
void specializeAppProcessPost(JNIEnv *env, jclass) {
|
||||
Context::GetInstance()->OnNativeForkAndSpecializePost(env);
|
||||
Context::GetInstance()->OnNativeForkAndSpecializePost(env, nice_name, app_data_dir);
|
||||
}
|
||||
}
|
||||
|
||||
RiruVersionedModuleInfo module{
|
||||
.moduleApiVersion = RIRU_MODULE_API_VERSION,
|
||||
.moduleApiVersion = apiVersion,
|
||||
.moduleInfo = RiruModuleInfo{
|
||||
.supportHide = !isDebug,
|
||||
.version = versionCode,
|
||||
|
|
@ -140,14 +125,11 @@ namespace lspd {
|
|||
};
|
||||
}
|
||||
|
||||
#define quote(s) #s
|
||||
#define str(s) quote(s)
|
||||
|
||||
RIRU_EXPORT RiruVersionedModuleInfo *init(Riru *riru) {
|
||||
LOGD("using riru %d", riru->riruApiVersion);
|
||||
LOGD("module path: %s", riru->magiskModulePath);
|
||||
lspd::magiskPath = riru->magiskModulePath;
|
||||
if (!lspd::isDebug && lspd::magiskPath.find(str(MODULE_NAME)) == std::string::npos) {
|
||||
if (!lspd::isDebug && lspd::magiskPath.find(lspd::moduleName) == std::string::npos) {
|
||||
LOGE("who am i");
|
||||
return nullptr;
|
||||
}
|
||||
|
|
@ -0,0 +1,242 @@
|
|||
/*
|
||||
* This file is part of LSPosed.
|
||||
*
|
||||
* LSPosed is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LSPosed is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LSPosed. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Copyright (C) 2021 LSPosed Contributors
|
||||
*/
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include "jni/zygisk.h"
|
||||
#include "logging.h"
|
||||
#include "context.h"
|
||||
#include "config.h"
|
||||
|
||||
namespace lspd {
|
||||
int *allowUnload = nullptr;
|
||||
|
||||
namespace {
|
||||
ssize_t xsendmsg(int sockfd, const struct msghdr *msg, int flags) {
|
||||
int sent = sendmsg(sockfd, msg, flags);
|
||||
if (sent < 0) {
|
||||
PLOGE("sendmsg");
|
||||
}
|
||||
return sent;
|
||||
}
|
||||
|
||||
ssize_t xrecvmsg(int sockfd, struct msghdr *msg, int flags) {
|
||||
int rec = recvmsg(sockfd, msg, flags);
|
||||
if (rec < 0) {
|
||||
PLOGE("recvmsg");
|
||||
}
|
||||
return rec;
|
||||
}
|
||||
|
||||
// Read exact same size as count
|
||||
ssize_t xxread(int fd, void *buf, size_t count) {
|
||||
size_t read_sz = 0;
|
||||
ssize_t ret;
|
||||
do {
|
||||
ret = read(fd, (std::byte *) buf + read_sz, count - read_sz);
|
||||
if (ret < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
PLOGE("read");
|
||||
return ret;
|
||||
}
|
||||
read_sz += ret;
|
||||
} while (read_sz != count && ret != 0);
|
||||
if (read_sz != count) {
|
||||
PLOGE("read (%zu != %zu)", count, read_sz);
|
||||
}
|
||||
return read_sz;
|
||||
}
|
||||
|
||||
// Write exact same size as count
|
||||
ssize_t xwrite(int fd, const void *buf, size_t count) {
|
||||
size_t write_sz = 0;
|
||||
ssize_t ret;
|
||||
do {
|
||||
ret = write(fd, (std::byte *) buf + write_sz, count - write_sz);
|
||||
if (ret < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
PLOGE("write");
|
||||
return ret;
|
||||
}
|
||||
write_sz += ret;
|
||||
} while (write_sz != count && ret != 0);
|
||||
if (write_sz != count) {
|
||||
PLOGE("write (%zu != %zu)", count, write_sz);
|
||||
}
|
||||
return write_sz;
|
||||
}
|
||||
|
||||
int send_fds(int sockfd, void *cmsgbuf, size_t bufsz, const int *fds, int cnt) {
|
||||
iovec iov = {
|
||||
.iov_base = &cnt,
|
||||
.iov_len = sizeof(cnt),
|
||||
};
|
||||
msghdr msg = {
|
||||
.msg_iov = &iov,
|
||||
.msg_iovlen = 1,
|
||||
};
|
||||
|
||||
if (cnt) {
|
||||
msg.msg_control = cmsgbuf;
|
||||
msg.msg_controllen = bufsz;
|
||||
cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
|
||||
cmsg->cmsg_len = CMSG_LEN(sizeof(int) * cnt);
|
||||
cmsg->cmsg_level = SOL_SOCKET;
|
||||
cmsg->cmsg_type = SCM_RIGHTS;
|
||||
|
||||
memcpy(CMSG_DATA(cmsg), fds, sizeof(int) * cnt);
|
||||
}
|
||||
|
||||
return xsendmsg(sockfd, &msg, 0);
|
||||
}
|
||||
|
||||
int send_fd(int sockfd, int fd) {
|
||||
if (fd < 0) {
|
||||
return send_fds(sockfd, nullptr, 0, nullptr, 0);
|
||||
}
|
||||
char cmsgbuf[CMSG_SPACE(sizeof(int))];
|
||||
return send_fds(sockfd, cmsgbuf, sizeof(cmsgbuf), &fd, 1);
|
||||
}
|
||||
|
||||
void *recv_fds(int sockfd, char *cmsgbuf, size_t bufsz, int cnt) {
|
||||
iovec iov = {
|
||||
.iov_base = &cnt,
|
||||
.iov_len = sizeof(cnt),
|
||||
};
|
||||
msghdr msg = {
|
||||
.msg_iov = &iov,
|
||||
.msg_iovlen = 1,
|
||||
.msg_control = cmsgbuf,
|
||||
.msg_controllen = bufsz
|
||||
};
|
||||
|
||||
xrecvmsg(sockfd, &msg, MSG_WAITALL);
|
||||
cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
|
||||
|
||||
if (msg.msg_controllen != bufsz ||
|
||||
cmsg == nullptr ||
|
||||
cmsg->cmsg_len != CMSG_LEN(sizeof(int) * cnt) ||
|
||||
cmsg->cmsg_level != SOL_SOCKET ||
|
||||
cmsg->cmsg_type != SCM_RIGHTS) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return CMSG_DATA(cmsg);
|
||||
}
|
||||
|
||||
int recv_fd(int sockfd) {
|
||||
char cmsgbuf[CMSG_SPACE(sizeof(int))];
|
||||
|
||||
void *data = recv_fds(sockfd, cmsgbuf, sizeof(cmsgbuf), 1);
|
||||
if (data == nullptr)
|
||||
return -1;
|
||||
|
||||
int result;
|
||||
memcpy(&result, data, sizeof(int));
|
||||
return result;
|
||||
}
|
||||
|
||||
int read_int(int fd) {
|
||||
int val;
|
||||
if (xxread(fd, &val, sizeof(val)) != sizeof(val))
|
||||
return -1;
|
||||
return val;
|
||||
}
|
||||
|
||||
void write_int(int fd, int val) {
|
||||
if (fd < 0) return;
|
||||
xwrite(fd, &val, sizeof(val));
|
||||
}
|
||||
}
|
||||
|
||||
class ZygiskModule : public zygisk::ModuleBase {
|
||||
JNIEnv *env_;
|
||||
|
||||
void onLoad(zygisk::Api *api, JNIEnv *env) override {
|
||||
env_ = env;
|
||||
Context::GetInstance()->Init();
|
||||
|
||||
// === workaround without companion ===
|
||||
using namespace std::string_literals;
|
||||
Context::GetInstance()->PreLoadDex("/data/adb/modules/"s + moduleName + "/framework/lspd.dex");
|
||||
// === end workaround ===
|
||||
|
||||
auto companion = api->connectCompanion();
|
||||
if (companion == -1) {
|
||||
LOGE("Failed to connect to companion");
|
||||
return;
|
||||
}
|
||||
|
||||
if (int fd; read_int(companion) == 0 && (fd = recv_fd(companion)) != -1) {
|
||||
Context::GetInstance()->PreLoadDex(
|
||||
"/proc/self/fd/" + std::to_string(fd));
|
||||
close(fd);
|
||||
} else {
|
||||
LOGE("Failed to read dex fd");
|
||||
}
|
||||
close(companion);
|
||||
}
|
||||
|
||||
void preAppSpecialize(zygisk::AppSpecializeArgs *args) override {
|
||||
Context::GetInstance()->OnNativeForkAndSpecializePre(
|
||||
env_, args->uid, args->gids, args->nice_name,
|
||||
args->is_child_zygote ? *args->is_child_zygote : false, args->app_data_dir);
|
||||
}
|
||||
|
||||
void postAppSpecialize(const zygisk::AppSpecializeArgs *args) override {
|
||||
Context::GetInstance()->OnNativeForkAndSpecializePost(env_, args->nice_name,
|
||||
args->app_data_dir);
|
||||
}
|
||||
|
||||
void preServerSpecialize(zygisk::ServerSpecializeArgs *args) override {
|
||||
Context::GetInstance()->OnNativeForkSystemServerPre(env_);
|
||||
}
|
||||
|
||||
void postServerSpecialize(const zygisk::ServerSpecializeArgs *args) override {
|
||||
Context::GetInstance()->OnNativeForkSystemServerPost(env_);
|
||||
}
|
||||
};
|
||||
|
||||
#define quote(s) #s
|
||||
|
||||
bool InitCompanion() {
|
||||
LOGD("onModuleLoaded: welcome to LSPosed!");
|
||||
LOGD("onModuleLoaded: version v%s (%d)", versionName, versionCode);
|
||||
return true;
|
||||
}
|
||||
|
||||
void CompanionEntry(int client) {
|
||||
using namespace std::string_literals;
|
||||
static bool inited = InitCompanion();
|
||||
static std::string path = "/data/adb/modules/"s + lspd::moduleName + "/" + kDexPath;
|
||||
static int fd = open(path.data(), O_RDONLY | O_CLOEXEC);
|
||||
if (inited && fd > 0) {
|
||||
write_int(client, 0);
|
||||
send_fd(client, fd);
|
||||
} else write_int(client, -1);
|
||||
close(client);
|
||||
}
|
||||
} //namespace lspd
|
||||
|
||||
REGISTER_ZYGISK_MODULE(lspd::ZygiskModule);
|
||||
|
||||
REGISTER_ZYGISK_COMPANION(lspd::CompanionEntry);
|
||||
|
|
@ -71,5 +71,7 @@ namespace lspd {
|
|||
}
|
||||
|
||||
extern const int versionCode;
|
||||
extern const int apiVersion;
|
||||
extern const char* const versionName;
|
||||
extern const char* const moduleName;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,6 +41,9 @@ namespace lspd {
|
|||
constexpr int SHARED_RELRO_UID = 1037;
|
||||
constexpr int PER_USER_RANGE = 100000;
|
||||
|
||||
static constexpr uid_t kAidInjected = INJECTED_AID;
|
||||
static constexpr uid_t kAidInet = 3003;
|
||||
|
||||
void Context::CallOnPostFixupStaticTrampolines(void *class_ptr) {
|
||||
if (!class_ptr || !class_linker_class_ || !post_fixup_static_mid_) [[unlikely]] {
|
||||
return;
|
||||
|
|
@ -55,24 +58,33 @@ namespace lspd {
|
|||
}
|
||||
}
|
||||
|
||||
void Context::PreLoadDex(std::string_view dex_path) {
|
||||
if (!dex.empty()) [[unlikely]] return;
|
||||
Context::PreloadedDex::PreloadedDex(FILE *f) {
|
||||
fseek(f, 0, SEEK_END);
|
||||
auto size = ftell(f);
|
||||
rewind(f);
|
||||
if (auto addr = mmap(nullptr, size, PROT_READ, MAP_SHARED, fileno(f), 0); addr) {
|
||||
addr_ = addr;
|
||||
size_ = size;
|
||||
} else {
|
||||
LOGE("Read dex failed");
|
||||
}
|
||||
}
|
||||
|
||||
FILE *f = fopen(dex_path.data(), "rb");
|
||||
if (!f) {
|
||||
Context::PreloadedDex::~PreloadedDex() {
|
||||
if (*this) munmap(addr_, size_);
|
||||
}
|
||||
|
||||
void Context::PreLoadDex(std::string_view dex_path) {
|
||||
if (dex_) [[unlikely]] return;
|
||||
|
||||
std::unique_ptr<FILE, decltype(&fclose)> f{fopen(dex_path.data(), "rb"), &fclose};
|
||||
|
||||
if (!f || !(dex_ = PreloadedDex(f.get()))) {
|
||||
LOGE("Fail to open dex from %s", dex_path.data());
|
||||
return;
|
||||
}
|
||||
fseek(f, 0, SEEK_END);
|
||||
dex.resize(ftell(f));
|
||||
rewind(f);
|
||||
if (dex.size() != fread(dex.data(), sizeof(decltype(dex)::value_type), dex.size(), f)) {
|
||||
LOGE("Read dex failed");
|
||||
dex.resize(0);
|
||||
}
|
||||
fclose(f);
|
||||
|
||||
LOGI("Loaded %s with size %zu", dex_path.data(), dex.size());
|
||||
LOGI("Loaded %s with size %zu", dex_path.data(), dex_.size());
|
||||
}
|
||||
|
||||
void Context::LoadDex(JNIEnv *env) {
|
||||
|
|
@ -88,6 +100,7 @@ namespace lspd {
|
|||
auto initMid = JNI_GetMethodID(env, in_memory_classloader, "<init>",
|
||||
"(Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V");
|
||||
auto byte_buffer_class = JNI_FindClass(env, "java/nio/ByteBuffer");
|
||||
auto dex = std::move(dex_);
|
||||
auto dex_buffer = env->NewDirectByteBuffer(dex.data(), dex.size());
|
||||
if (auto my_cl = JNI_NewObject(env, in_memory_classloader, initMid,
|
||||
dex_buffer, sys_classloader)) {
|
||||
|
|
@ -102,6 +115,10 @@ namespace lspd {
|
|||
env->GetJavaVM(&vm_);
|
||||
}
|
||||
|
||||
void Context::Init() {
|
||||
InitSymbolCache();
|
||||
}
|
||||
|
||||
void Context::Init(JNIEnv *env) {
|
||||
if (auto class_linker_class = FindClassFromCurrentLoader(env, kClassLinkerClassName)) {
|
||||
class_linker_class_ = JNI_NewGlobalRef(env, class_linker_class);
|
||||
|
|
@ -110,7 +127,8 @@ namespace lspd {
|
|||
"onPostFixupStaticTrampolines",
|
||||
"(Ljava/lang/Class;)V");
|
||||
|
||||
if (auto entry_class = FindClassFromLoader(env, GetCurrentClassLoader(), kEntryClassName)) {
|
||||
if (auto entry_class = FindClassFromLoader(env, GetCurrentClassLoader(),
|
||||
kEntryClassName)) {
|
||||
entry_class_ = JNI_NewGlobalRef(env, entry_class);
|
||||
}
|
||||
|
||||
|
|
@ -122,13 +140,16 @@ namespace lspd {
|
|||
}
|
||||
|
||||
ScopedLocalRef<jclass>
|
||||
Context::FindClassFromLoader(JNIEnv *env, jobject class_loader, std::string_view class_name) {
|
||||
Context::FindClassFromLoader(JNIEnv *env, jobject class_loader,
|
||||
std::string_view class_name) {
|
||||
if (class_loader == nullptr) return {env, nullptr};
|
||||
static auto clz = JNI_NewGlobalRef(env, JNI_FindClass(env, "dalvik/system/DexClassLoader"));
|
||||
static auto clz = JNI_NewGlobalRef(env,
|
||||
JNI_FindClass(env, "dalvik/system/DexClassLoader"));
|
||||
static jmethodID mid = JNI_GetMethodID(env, clz, "loadClass",
|
||||
"(Ljava/lang/String;)Ljava/lang/Class;");
|
||||
if (!mid) {
|
||||
mid = JNI_GetMethodID(env, clz, "findClass", "(Ljava/lang/String;)Ljava/lang/Class;");
|
||||
mid = JNI_GetMethodID(env, clz, "findClass",
|
||||
"(Ljava/lang/String;)Ljava/lang/Class;");
|
||||
}
|
||||
if (mid) [[likely]] {
|
||||
auto target = JNI_CallObjectMethod(env, class_loader, mid,
|
||||
|
|
@ -167,8 +188,7 @@ namespace lspd {
|
|||
}
|
||||
|
||||
void
|
||||
Context::OnNativeForkSystemServerPost(JNIEnv *env, jint res) {
|
||||
if (res != 0) return;
|
||||
Context::OnNativeForkSystemServerPost(JNIEnv *env) {
|
||||
if (!skip_) {
|
||||
LoadDex(env);
|
||||
Service::instance()->HookBridge(*this, env);
|
||||
|
|
@ -184,13 +204,22 @@ namespace lspd {
|
|||
|
||||
void Context::OnNativeForkAndSpecializePre(JNIEnv *env,
|
||||
jint uid,
|
||||
jintArray &gids,
|
||||
jstring nice_name,
|
||||
jboolean is_child_zygote,
|
||||
jstring app_data_dir) {
|
||||
if (uid == kAidInjected) {
|
||||
int array_size = gids ? env->GetArrayLength(gids) : 0;
|
||||
auto region = std::make_unique<jint[]>(array_size + 1);
|
||||
auto *new_gids = env->NewIntArray(array_size + 1);
|
||||
if (gids) env->GetIntArrayRegion(gids, 0, array_size, region.get());
|
||||
region.get()[array_size] = kAidInet;
|
||||
env->SetIntArrayRegion(new_gids, 0, array_size + 1, region.get());
|
||||
if (gids) env->SetIntArrayRegion(gids, 0, 1, region.get() + array_size);
|
||||
gids = new_gids;
|
||||
}
|
||||
Service::instance()->InitService(env);
|
||||
const auto app_id = uid % PER_USER_RANGE;
|
||||
app_data_dir_ = app_data_dir;
|
||||
nice_name_ = nice_name;
|
||||
JUTFString process_name(env, nice_name);
|
||||
skip_ = !sym_initialized;
|
||||
if (!skip_ && !app_data_dir) {
|
||||
|
|
@ -213,10 +242,11 @@ namespace lspd {
|
|||
}
|
||||
|
||||
void
|
||||
Context::OnNativeForkAndSpecializePost(JNIEnv *env) {
|
||||
const JUTFString process_name(env, nice_name_);
|
||||
Context::OnNativeForkAndSpecializePost(JNIEnv *env, jstring nice_name,
|
||||
jstring app_data_dir) {
|
||||
const JUTFString process_name(env, nice_name);
|
||||
auto binder = skip_ ? ScopedLocalRef<jobject>{env, nullptr}
|
||||
: Service::instance()->RequestBinder(env, nice_name_);
|
||||
: Service::instance()->RequestBinder(env, nice_name);
|
||||
if (binder) {
|
||||
InstallInlineHooks();
|
||||
LoadDex(env);
|
||||
|
|
@ -224,7 +254,7 @@ namespace lspd {
|
|||
LOGD("Done prepare");
|
||||
FindAndCall(env, "forkAndSpecializePost",
|
||||
"(Ljava/lang/String;Ljava/lang/String;Landroid/os/IBinder;)V",
|
||||
app_data_dir_, nice_name_,
|
||||
app_data_dir, nice_name,
|
||||
binder);
|
||||
LOGD("injected xposed into %s", process_name.get());
|
||||
setAllowUnload(false);
|
||||
|
|
|
|||
|
|
@ -45,31 +45,70 @@ namespace lspd {
|
|||
|
||||
void CallOnPostFixupStaticTrampolines(void *class_ptr);
|
||||
|
||||
inline ScopedLocalRef<jclass> FindClassFromCurrentLoader(JNIEnv *env, std::string_view className) const {
|
||||
inline ScopedLocalRef<jclass>
|
||||
FindClassFromCurrentLoader(JNIEnv *env, std::string_view className) const {
|
||||
return FindClassFromLoader(env, GetCurrentClassLoader(), className);
|
||||
};
|
||||
|
||||
void OnNativeForkAndSpecializePre(JNIEnv *env, jint uid, jstring nice_name, jboolean is_child_zygote, jstring app_data_dir);
|
||||
void OnNativeForkAndSpecializePre(JNIEnv *env, jint uid, jintArray &gids, jstring nice_name,
|
||||
jboolean is_child_zygote, jstring app_data_dir);
|
||||
|
||||
void OnNativeForkAndSpecializePost(JNIEnv *env);
|
||||
void OnNativeForkAndSpecializePost(JNIEnv *env, jstring nice_name, jstring app_data_dir);
|
||||
|
||||
void OnNativeForkSystemServerPost(JNIEnv *env, jint res);
|
||||
void OnNativeForkSystemServerPost(JNIEnv *env);
|
||||
|
||||
void OnNativeForkSystemServerPre(JNIEnv *env);
|
||||
|
||||
void PreLoadDex(std::string_view dex_paths);
|
||||
|
||||
void Init();
|
||||
|
||||
private:
|
||||
inline static std::unique_ptr<Context> instance_ = std::make_unique<Context>();
|
||||
jobject inject_class_loader_ = nullptr;
|
||||
jclass entry_class_ = nullptr;
|
||||
jstring app_data_dir_ = nullptr;
|
||||
jstring nice_name_ = nullptr;
|
||||
JavaVM *vm_ = nullptr;
|
||||
jclass class_linker_class_ = nullptr;
|
||||
jmethodID post_fixup_static_mid_ = nullptr;
|
||||
bool skip_ = false;
|
||||
std::vector<std::byte> dex{};
|
||||
|
||||
struct PreloadedDex {
|
||||
|
||||
PreloadedDex() : addr_(nullptr), size_(0) {}
|
||||
|
||||
PreloadedDex(const PreloadedDex &) = delete;
|
||||
|
||||
PreloadedDex &operator=(const PreloadedDex &) = delete;
|
||||
|
||||
PreloadedDex &operator=(PreloadedDex &&other) {
|
||||
addr_ = other.addr_;
|
||||
size_ = other.size_;
|
||||
other.addr_ = nullptr;
|
||||
other.size_ = 0;
|
||||
return *this;
|
||||
}
|
||||
|
||||
PreloadedDex(PreloadedDex &&other) : addr_(other.addr_), size_(other.size_) {
|
||||
other.addr_ = nullptr;
|
||||
other.size_ = 0;
|
||||
};
|
||||
|
||||
operator bool() const { return addr_ && size_; }
|
||||
|
||||
auto size() const { return size_; }
|
||||
|
||||
auto data() const { return addr_; }
|
||||
|
||||
PreloadedDex(FILE *f);
|
||||
|
||||
~PreloadedDex();
|
||||
|
||||
private:
|
||||
void *addr_;
|
||||
std::size_t size_;
|
||||
};
|
||||
|
||||
PreloadedDex dex_{};
|
||||
|
||||
Context() {}
|
||||
|
||||
|
|
@ -78,11 +117,13 @@ namespace lspd {
|
|||
void Init(JNIEnv *env);
|
||||
|
||||
static ScopedLocalRef<jclass> FindClassFromLoader(JNIEnv *env, jobject class_loader,
|
||||
std::string_view class_name);
|
||||
std::string_view class_name);
|
||||
|
||||
static void setAllowUnload(bool unload);
|
||||
|
||||
template<typename ...Args>
|
||||
void FindAndCall(JNIEnv *env, std::string_view method_name, std::string_view method_sig, Args&&... args) const;
|
||||
void FindAndCall(JNIEnv *env, std::string_view method_name, std::string_view method_sig,
|
||||
Args &&... args) const;
|
||||
|
||||
friend std::unique_ptr<Context> std::make_unique<Context>();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,282 @@
|
|||
// All content of this file is released to the public domain.
|
||||
|
||||
// This file is the public API for Zygisk modules.
|
||||
// DO NOT use this file for developing Zygisk modules as it might contain WIP changes.
|
||||
// Always use the following header for development as those are finalized APIs:
|
||||
// https://github.com/topjohnwu/zygisk-module-sample/blob/master/module/jni/zygisk.hpp
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#define ZYGISK_API_VERSION 1
|
||||
|
||||
/*
|
||||
Define a class and inherit zygisk::ModuleBase to implement the functionality of your module.
|
||||
Use the macro REGISTER_ZYGISK_MODULE(className) to register that class to Zygisk.
|
||||
Please note that modules will only be loaded after zygote has forked the child process.
|
||||
THIS MEANS ALL OF YOUR CODE RUNS IN THE APP/SYSTEM SERVER PROCESS, NOT THE ZYGOTE DAEMON!
|
||||
Example code:
|
||||
static jint (*orig_logger_entry_max)(JNIEnv *env);
|
||||
static jint my_logger_entry_max(JNIEnv *env) { return orig_logger_entry_max(env); }
|
||||
static void example_handler(int socket) { ... }
|
||||
class ExampleModule : public zygisk::ModuleBase {
|
||||
public:
|
||||
void onLoad(zygisk::Api *api, JNIEnv *env) override {
|
||||
this->api = api;
|
||||
}
|
||||
void preAppSpecialize(zygisk::AppSpecializeArgs *args) override {
|
||||
JNINativeMethod methods[] = {
|
||||
{ "logger_entry_max_payload_native", "()I", (void*) my_logger_entry_max },
|
||||
};
|
||||
api->hookJniNativeMethods("android/util/Log", methods, 1);
|
||||
*(void **) &orig_logger_entry_max = methods[0].fnPtr;
|
||||
}
|
||||
private:
|
||||
zygisk::Api *api;
|
||||
};
|
||||
REGISTER_ZYGISK_MODULE(ExampleModule)
|
||||
REGISTER_ZYGISK_COMPANION(example_handler)
|
||||
*/
|
||||
|
||||
namespace zygisk {
|
||||
|
||||
struct Api;
|
||||
struct AppSpecializeArgs;
|
||||
struct ServerSpecializeArgs;
|
||||
|
||||
class ModuleBase {
|
||||
public:
|
||||
|
||||
// This function is called when the module is loaded into the target process.
|
||||
// A Zygisk API handle will be sent as an argument; call utility functions or interface
|
||||
// with Zygisk through this handle.
|
||||
virtual void onLoad(Api *api, JNIEnv *env) {}
|
||||
|
||||
// This function is called before the app process is specialized.
|
||||
// At this point, the process just got forked from zygote, but no app specific specialization
|
||||
// is applied. This means that the process does not have any sandbox restrictions and
|
||||
// still runs with the same privilege of zygote.
|
||||
//
|
||||
// All the arguments that will be sent and used for app specialization is passed as a single
|
||||
// AppSpecializeArgs object. You can read and overwrite these arguments to change how the app
|
||||
// process will be specialized.
|
||||
//
|
||||
// If you need to run some operations as superuser, you can call Api::connectCompanion() to
|
||||
// get a socket to do IPC calls with a root companion process.
|
||||
// See Api::connectCompanion() for more info.
|
||||
virtual void preAppSpecialize(AppSpecializeArgs *args) {}
|
||||
|
||||
// This function is called after the app process is specialized.
|
||||
// At this point, the process has all sandbox restrictions enabled for this application.
|
||||
// This means that this function runs as the same privilege of the app's own code.
|
||||
virtual void postAppSpecialize(const AppSpecializeArgs *args) {}
|
||||
|
||||
// This function is called before the system server process is specialized.
|
||||
// See preAppSpecialize(args) for more info.
|
||||
virtual void preServerSpecialize(ServerSpecializeArgs *args) {}
|
||||
|
||||
// This function is called after the app process is specialized.
|
||||
// At this point, the process runs with the privilege of system_server.
|
||||
virtual void postServerSpecialize(const ServerSpecializeArgs *args) {}
|
||||
};
|
||||
|
||||
struct AppSpecializeArgs {
|
||||
// Required arguments. These arguments are guaranteed to exist on all Android versions.
|
||||
jint &uid;
|
||||
jint &gid;
|
||||
jintArray &gids;
|
||||
jint &runtime_flags;
|
||||
jint &mount_external;
|
||||
jstring &se_info;
|
||||
jstring &nice_name;
|
||||
jstring &instruction_set;
|
||||
jstring &app_data_dir;
|
||||
|
||||
// Optional arguments. Please check whether the pointer is null before de-referencing
|
||||
jboolean *const is_child_zygote;
|
||||
jboolean *const is_top_app;
|
||||
jobjectArray *const pkg_data_info_list;
|
||||
jobjectArray *const whitelisted_data_info_list;
|
||||
jboolean *const mount_data_dirs;
|
||||
jboolean *const mount_storage_dirs;
|
||||
|
||||
AppSpecializeArgs() = delete;
|
||||
};
|
||||
|
||||
struct ServerSpecializeArgs {
|
||||
jint &uid;
|
||||
jint &gid;
|
||||
jintArray &gids;
|
||||
jint &runtime_flags;
|
||||
jlong &permitted_capabilities;
|
||||
jlong &effective_capabilities;
|
||||
|
||||
ServerSpecializeArgs() = delete;
|
||||
};
|
||||
|
||||
namespace internal {
|
||||
struct api_table;
|
||||
template <class T> void entry_impl(api_table *, JNIEnv *);
|
||||
}
|
||||
|
||||
struct Api {
|
||||
|
||||
// Connect to a root companion process and get a Unix domain socket for IPC.
|
||||
//
|
||||
// This API only works in the pre[XXX]Specialize functions due to SELinux restrictions.
|
||||
//
|
||||
// The pre[XXX]Specialize functions run with the same privilege of zygote.
|
||||
// If you would like to do some operations with superuser permissions, register a handler
|
||||
// function that would be called in the root process with REGISTER_ZYGISK_COMPANION(func).
|
||||
// Another good use case for a companion process is that if you want to share some resources
|
||||
// across multiple processes, hold the resources in the companion process and pass it over.
|
||||
//
|
||||
// When this function is called, in the companion process, a socket pair will be created,
|
||||
// your module's onCompanionRequest(int) callback will receive one socket, and the other
|
||||
// socket will be returned.
|
||||
//
|
||||
// Returns a file descriptor to a socket that is connected to the socket passed to
|
||||
// your module's onCompanionRequest(int). Returns -1 if the connection attempt failed.
|
||||
int connectCompanion();
|
||||
|
||||
// Force Magisk's denylist unmount routines to run on this process.
|
||||
//
|
||||
// This API only works in preAppSpecialize.
|
||||
//
|
||||
// Processes added to Magisk's denylist will have all Magisk and its modules' files unmounted
|
||||
// from its mount namespace. In addition, all Zygisk code will be unloaded from memory, which
|
||||
// also implies that no Zygisk modules (including yours) are loaded.
|
||||
//
|
||||
// However, if for any reason your module still wants the unmount part of the denylist
|
||||
// operation to be enabled EVEN IF THE PROCESS IS NOT ON THE DENYLIST, call this function.
|
||||
// No code will be unloaded from memory (including your module) because there is no way to
|
||||
// guarantee no crashes will occur.
|
||||
//
|
||||
// The unmounting does not happen immediately after the function is called. It is actually
|
||||
// done during app process specialization.
|
||||
void forceDenyListUnmount();
|
||||
|
||||
// Hook JNI native methods for a class
|
||||
//
|
||||
// Lookup all registered JNI native methods and replace it with your own functions.
|
||||
// The original function pointer will be saved in each JNINativeMethod's fnPtr.
|
||||
// If no matching class, method name, or signature is found, that specific JNINativeMethod.fnPtr
|
||||
// will be set to nullptr.
|
||||
void hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int numMethods);
|
||||
|
||||
// For ELFs loaded in memory matching `regex`, replace function `symbol` with `newFunc`.
|
||||
// If `oldFunc` is not nullptr, the original function pointer will be saved to `oldFunc`.
|
||||
void pltHookRegister(const char *regex, const char *symbol, void *newFunc, void **oldFunc);
|
||||
|
||||
// For ELFs loaded in memory matching `regex`, exclude hooks registered for `symbol`.
|
||||
// If `symbol` is nullptr, then all symbols will be excluded.
|
||||
void pltHookExclude(const char *regex, const char *symbol);
|
||||
|
||||
// Commit all the hooks that was previously registered.
|
||||
// Returns false if an error occurred.
|
||||
bool pltHookCommit();
|
||||
|
||||
private:
|
||||
internal::api_table *impl;
|
||||
template <class T> friend void internal::entry_impl(internal::api_table *, JNIEnv *);
|
||||
};
|
||||
|
||||
// Register a class as a Zygisk module
|
||||
|
||||
#define REGISTER_ZYGISK_MODULE(clazz) \
|
||||
void zygisk_module_entry(zygisk::internal::api_table *table, JNIEnv *env) { \
|
||||
zygisk::internal::entry_impl<clazz>(table, env); \
|
||||
}
|
||||
|
||||
// Register a root companion request handler function for your module
|
||||
//
|
||||
// The function runs in a superuser daemon process and handles a root companion request from
|
||||
// your module running in a target process. The function has to accept an integer value,
|
||||
// which is a socket that is connected to the target process.
|
||||
// See Api::connectCompanion() for more info.
|
||||
//
|
||||
// NOTE: the function can run concurrently on multiple threads.
|
||||
// Be aware of race conditions if you have a globally shared resource.
|
||||
|
||||
#define REGISTER_ZYGISK_COMPANION(func) \
|
||||
void zygisk_companion_entry(int client) { func(client); }
|
||||
|
||||
/************************************************************************************
|
||||
* All the code after this point is internal code used to interface with Zygisk
|
||||
* and guarantee ABI stability. You do not have to understand what it is doing.
|
||||
************************************************************************************/
|
||||
|
||||
namespace internal {
|
||||
|
||||
struct module_abi {
|
||||
long api_version;
|
||||
ModuleBase *_this;
|
||||
|
||||
void (*preAppSpecialize)(ModuleBase *, AppSpecializeArgs *);
|
||||
void (*postAppSpecialize)(ModuleBase *, const AppSpecializeArgs *);
|
||||
void (*preServerSpecialize)(ModuleBase *, ServerSpecializeArgs *);
|
||||
void (*postServerSpecialize)(ModuleBase *, const ServerSpecializeArgs *);
|
||||
|
||||
module_abi(ModuleBase *module) : api_version(ZYGISK_API_VERSION), _this(module) {
|
||||
preAppSpecialize = [](auto self, auto args) { self->preAppSpecialize(args); };
|
||||
postAppSpecialize = [](auto self, auto args) { self->postAppSpecialize(args); };
|
||||
preServerSpecialize = [](auto self, auto args) { self->preServerSpecialize(args); };
|
||||
postServerSpecialize = [](auto self, auto args) { self->postServerSpecialize(args); };
|
||||
}
|
||||
};
|
||||
|
||||
struct api_table {
|
||||
// These first 2 entries are permanent, shall never change
|
||||
void *_this;
|
||||
bool (*registerModule)(api_table *, module_abi *);
|
||||
|
||||
// Utility functions
|
||||
void (*hookJniNativeMethods)(JNIEnv *, const char *, JNINativeMethod *, int);
|
||||
void (*pltHookRegister)(const char *, const char *, void *, void **);
|
||||
void (*pltHookExclude)(const char *, const char *);
|
||||
bool (*pltHookCommit)();
|
||||
|
||||
// Zygisk functions
|
||||
int (*connectCompanion)(void * /* _this */);
|
||||
void (*forceDenyListUnmount)(void * /* _this */);
|
||||
};
|
||||
|
||||
template <class T>
|
||||
void entry_impl(api_table *table, JNIEnv *env) {
|
||||
ModuleBase *module = new T();
|
||||
if (!table->registerModule(table, new module_abi(module)))
|
||||
return;
|
||||
auto api = new Api();
|
||||
api->impl = table;
|
||||
module->onLoad(api, env);
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
|
||||
int Api::connectCompanion() {
|
||||
return impl->connectCompanion(impl->_this);
|
||||
}
|
||||
void Api::forceDenyListUnmount() {
|
||||
impl->forceDenyListUnmount(impl->_this);
|
||||
}
|
||||
void Api::hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int numMethods) {
|
||||
impl->hookJniNativeMethods(env, className, methods, numMethods);
|
||||
}
|
||||
void Api::pltHookRegister(const char *regex, const char *symbol, void *newFunc, void **oldFunc) {
|
||||
impl->pltHookRegister(regex, symbol, newFunc, oldFunc);
|
||||
}
|
||||
void Api::pltHookExclude(const char *regex, const char *symbol) {
|
||||
impl->pltHookExclude(regex, symbol);
|
||||
}
|
||||
bool Api::pltHookCommit() {
|
||||
return impl->pltHookCommit();
|
||||
}
|
||||
|
||||
} // namespace zygisk
|
||||
|
||||
[[gnu::visibility("default")]] [[gnu::used]]
|
||||
extern "C" void zygisk_module_entry(zygisk::internal::api_table *, JNIEnv *);
|
||||
|
||||
[[gnu::visibility("default")]] [[gnu::used]]
|
||||
extern "C" void zygisk_companion_entry(int);
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
#include "config.h"
|
||||
|
||||
namespace lspd {
|
||||
const int versionCode = ${VERSION_CODE};
|
||||
const char* const versionName = "${VERSION_NAME}";
|
||||
}
|
||||
|
|
@ -50,6 +50,7 @@ public class ConfigFileManager {
|
|||
private static final Path lockPath = basePath.resolve("lock");
|
||||
private static final Path configDirPath = basePath.resolve("config");
|
||||
static final File dbPath = configDirPath.resolve("modules_config.db").toFile();
|
||||
static final File magiskDbPath = new File("/data/adb/magisk.db");
|
||||
private static final Path logDirPath = basePath.resolve("log");
|
||||
private static final Path oldLogDirPath = basePath.resolve("log.old");
|
||||
private static final DateTimeFormatter formatter =
|
||||
|
|
|
|||
|
|
@ -459,6 +459,8 @@ public class ConfigManager {
|
|||
final var obsoleteModules = new HashSet<Application>();
|
||||
final var moduleAvailability = new HashMap<Pair<String, Integer>, Boolean>();
|
||||
final var cachedProcessScope = new HashMap<Pair<String, Integer>, List<ProcessScope>>();
|
||||
|
||||
final var denylist = new HashSet<>(getDenyListPackages());
|
||||
while (cursor.moveToNext()) {
|
||||
Application app = new Application();
|
||||
app.packageName = cursor.getString(appPkgNameIdx);
|
||||
|
|
@ -488,6 +490,8 @@ public class ConfigManager {
|
|||
try {
|
||||
List<ProcessScope> processesScope = cachedProcessScope.computeIfAbsent(new Pair<>(app.packageName, app.userId), (k) -> {
|
||||
try {
|
||||
if (denylist.contains(app.packageName))
|
||||
Log.w(TAG, app.packageName + " is on denylist. It may not take effect.");
|
||||
return getAssociatedProcesses(app);
|
||||
} catch (RemoteException e) {
|
||||
return Collections.emptyList();
|
||||
|
|
@ -920,4 +924,23 @@ public class ConfigManager {
|
|||
public boolean isSepolicyLoaded() {
|
||||
return sepolicyLoaded;
|
||||
}
|
||||
|
||||
public static List<String> getDenyListPackages() {
|
||||
List<String> result = new ArrayList<>();
|
||||
try {
|
||||
final SQLiteDatabase magiskDb =
|
||||
SQLiteDatabase.openDatabase(ConfigFileManager.magiskDbPath, new SQLiteDatabase.OpenParams.Builder().addOpenFlags(SQLiteDatabase.OPEN_READONLY).build());
|
||||
try (Cursor cursor = magiskDb.query(true, "denylist", new String[]{"package_name"}, null, null, null, null, null, null, null)) {
|
||||
if (cursor == null) return result;
|
||||
int packageNameIdx = cursor.getColumnIndex("package_name");
|
||||
while (cursor.moveToNext()) {
|
||||
result.add(cursor.getString(packageNameIdx));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
Log.e(TAG, "get denylist", e);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -719,6 +719,11 @@ public class LSPManagerService extends ILSPManagerService.Stub {
|
|||
setAddShortcut(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getDenyListPackages() {
|
||||
return ConfigManager.getDenyListPackages();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flashZip(String zipPath, ParcelFileDescriptor outputStream) {
|
||||
var processBuilder = new ProcessBuilder("magisk", "--install-module", zipPath);
|
||||
|
|
|
|||
|
|
@ -75,4 +75,6 @@ interface ILSPManagerService {
|
|||
oneway void flashZip(String zipPath, in ParcelFileDescriptor outputStream) = 39;
|
||||
|
||||
boolean performDexOptMode(String packageName) = 40;
|
||||
|
||||
List<String> getDenyListPackages() = 41;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue