[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:
LoveSy 2021-10-17 03:44:26 +08:00 committed by GitHub
parent 24c468164e
commit 7a7c0c56e3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 978 additions and 206 deletions

View File

@ -71,27 +71,40 @@ jobs:
echo 'org.gradle.vfs.watch=true' >> gradle.properties echo 'org.gradle.vfs.watch=true' >> gradle.properties
echo 'org.gradle.jvmargs=-Xmx2048m' >> gradle.properties echo 'org.gradle.jvmargs=-Xmx2048m' >> gradle.properties
echo 'android.native.buildOutput=verbose' >> gradle.properties echo 'android.native.buildOutput=verbose' >> gradle.properties
./gradlew zipRelease zipDebug ./gradlew zipAll
ccache -s ccache -s
- name: Prepare artifact - name: Prepare artifact
if: success() if: success()
id: prepareArtifact id: prepareArtifact
run: | run: |
releaseName=`ls core/release/LSPosed-v*-release.zip | awk -F '(/|.zip)' '{print $3}'` && echo "::set-output name=releaseName::$releaseName" riruReleaseName=`ls core/release/LSPosed-riru-v*-release.zip | awk -F '(/|.zip)' '{print $3}'` && echo "::set-output name=riruReleaseName::$riruReleaseName"
debugName=`ls core/release/LSPosed-v*-debug.zip | awk -F '(/|.zip)' '{print $3}'` && echo "::set-output name=debugName::$debugName" riruDebugName=`ls core/release/LSPosed-riru-v*-debug.zip | awk -F '(/|.zip)' '{print $3}'` && echo "::set-output name=riruDebugName::$riruDebugName"
unzip core/release/LSPosed-v*-release.zip -d LSPosed-release zygiskReleaseName=`ls core/release/LSPosed-zygisk-v*-release.zip | awk -F '(/|.zip)' '{print $3}'` && echo "::set-output name=zygiskReleaseName::$zygiskReleaseName"
unzip core/release/LSPosed-v*-debug.zip -d LSPosed-debug zygiskDebugName=`ls core/release/LSPosed-zygisk-v*-debug.zip | awk -F '(/|.zip)' '{print $3}'` && echo "::set-output name=zygiskDebugName::$zygiskDebugName"
- name: Upload release 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 uses: actions/upload-artifact@v2
with: with:
name: ${{ steps.prepareArtifact.outputs.releaseName }} name: ${{ steps.prepareArtifact.outputs.riruReleaseName }}
path: './LSPosed-release/*' path: './LSPosed-riru-release/*'
- name: Upload debug - name: Upload riru debug
# if: ${{ github.event_name == 'pull_request' && success() }}
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2
with: with:
name: ${{ steps.prepareArtifact.outputs.debugName }} name: ${{ steps.prepareArtifact.outputs.riruDebugName }}
path: './LSPosed-debug/*' 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 - name: Upload mappings
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2
with: with:
@ -108,7 +121,9 @@ jobs:
COMMIT_URL: ${{ github.event.head_commit.url }} COMMIT_URL: ${{ github.event.head_commit.url }}
run: | run: |
OUTPUT="core/release/" OUTPUT="core/release/"
export release=$(find $OUTPUT -name "LSPosed-v*-release.zip") export riruRelease=$(find $OUTPUT -name "LSPosed-riru-v*-release.zip")
export debug=$(find $OUTPUT -name "LSPosed-v*-debug.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"])))'` 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"

View File

@ -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) { public static void flashZip(String zipPath, ParcelFileDescriptor outputStream) {
try { try {
LSPManagerServiceHolder.getService().flashZip(zipPath, outputStream); LSPManagerServiceHolder.getService().flashZip(zipPath, outputStream);

View File

@ -39,6 +39,7 @@ public class AppHelper {
public static final String SETTINGS_CATEGORY = "de.robv.android.xposed.category.MODULE_SETTINGS"; public static final String SETTINGS_CATEGORY = "de.robv.android.xposed.category.MODULE_SETTINGS";
public static final int FLAG_SHOW_FOR_ALL_USERS = 0x0400; public static final int FLAG_SHOW_FOR_ALL_USERS = 0x0400;
private static List<String> denyList;
private static List<PackageInfo> appList; private static List<PackageInfo> appList;
public static Intent getSettingsIntent(String packageName, int userId) { public static Intent getSettingsIntent(String packageName, int userId) {
@ -137,4 +138,11 @@ public class AppHelper {
} }
return appList; return appList;
} }
public static List<String> getDenyList(boolean force) {
if (denyList == null || force) {
denyList = ConfigManager.getDenyListPackages();
}
return denyList;
}
} }

View File

@ -105,6 +105,7 @@ public class ScopeAdapter extends RecyclerView.Adapter<ScopeAdapter.ViewHolder>
private final HashSet<ApplicationWithEquals> checkedList = new HashSet<>(); private final HashSet<ApplicationWithEquals> checkedList = new HashSet<>();
private final ConcurrentLinkedQueue<AppInfo> searchList = new ConcurrentLinkedQueue<>(); private final ConcurrentLinkedQueue<AppInfo> searchList = new ConcurrentLinkedQueue<>();
private final List<AppInfo> showList = new ArrayList<>(); private final List<AppInfo> showList = new ArrayList<>();
private final List<String> denyList = new ArrayList<>();
private final SwitchBar.OnCheckedChangeListener switchBarOnCheckedChangeListener = new SwitchBar.OnCheckedChangeListener() { private final SwitchBar.OnCheckedChangeListener switchBarOnCheckedChangeListener = new SwitchBar.OnCheckedChangeListener() {
@Override @Override
@ -161,6 +162,11 @@ public class ScopeAdapter extends RecyclerView.Adapter<ScopeAdapter.ViewHolder>
if (checkedList.contains(app)) { if (checkedList.contains(app)) {
return false; return false;
} }
if (preferences.getBoolean("filter_denylist", false)) {
if (denyList.contains(info.packageName)) {
return true;
}
}
if (preferences.getBoolean("filter_modules", true)) { if (preferences.getBoolean("filter_modules", true)) {
if (info.applicationInfo.metaData != null && info.applicationInfo.metaData.containsKey("xposedminversion")) { if (info.applicationInfo.metaData != null && info.applicationInfo.metaData.containsKey("xposedminversion")) {
return true; return true;
@ -248,6 +254,9 @@ public class ScopeAdapter extends RecyclerView.Adapter<ScopeAdapter.ViewHolder>
} else if (itemId == R.id.item_filter_modules) { } else if (itemId == R.id.item_filter_modules) {
item.setChecked(!item.isChecked()); item.setChecked(!item.isChecked());
preferences.edit().putBoolean("filter_modules", item.isChecked()).apply(); 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) { } else if (itemId == R.id.menu_launch) {
Intent launchIntent = AppHelper.getSettingsIntent(module.packageName, module.userId); Intent launchIntent = AppHelper.getSettingsIntent(module.packageName, module.userId);
if (launchIntent != null) { 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_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_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_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)) { switch (preferences.getInt("list_sort", 0)) {
case 7: case 7:
menu.findItem(R.id.item_sort_by_update_time).setChecked(true); menu.findItem(R.id.item_sort_by_update_time).setChecked(true);
@ -354,8 +364,9 @@ public class ScopeAdapter extends RecyclerView.Adapter<ScopeAdapter.ViewHolder>
@Override @Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) { public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
holder.root.setAlpha(enabled ? 1.0f : .5f);
AppInfo appInfo = showList.get(position); 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"); boolean android = appInfo.packageName.equals("android");
CharSequence appName; CharSequence appName;
int userId = appInfo.applicationInfo.uid / 100000; int userId = appInfo.applicationInfo.uid / 100000;
@ -403,6 +414,21 @@ public class ScopeAdapter extends RecyclerView.Adapter<ScopeAdapter.ViewHolder>
} else { } else {
holder.hint.setVisibility(View.GONE); 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) -> { holder.itemView.setOnCreateContextMenuListener((menu, v, menuInfo) -> {
activity.getMenuInflater().inflate(R.menu.menu_app_item, menu); 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); List<PackageInfo> appList = AppHelper.getAppList(force);
checkedList.clear(); checkedList.clear();
recommendedList.clear(); recommendedList.clear();
denyList.clear();
denyList.addAll(AppHelper.getDenyList(force));
var tmpList = new ArrayList<AppInfo>(); var tmpList = new ArrayList<AppInfo>();
checkedList.addAll(ConfigManager.getModuleScope(module.packageName)); checkedList.addAll(ConfigManager.getModuleScope(module.packageName));
HashSet<ApplicationWithEquals> installedList = new HashSet<>(); 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) Snackbar.make(fragment.binding.snackbar, R.string.reboot_required, Snackbar.LENGTH_SHORT)
.setAction(R.string.reboot, v -> ConfigManager.reboot(false)) .setAction(R.string.reboot, v -> ConfigManager.reboot(false))
.show(); .show();
} else if (denyList.contains(appInfo.packageName)) {
Snackbar.make(fragment.binding.snackbar, activity.getString(R.string.deny_list, appInfo.label), Snackbar.LENGTH_SHORT)
.show();
} }
} }

View File

@ -58,6 +58,12 @@
android:checkable="true" android:checkable="true"
android:checked="true" android:checked="true"
android:title="@string/menu_show_system_apps" /> 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> </menu>
</item> </item>

View File

@ -124,6 +124,7 @@
<string name="no_scope_selected">You did not select any app. Continue?</string> <string name="no_scope_selected">You did not select any app. Continue?</string>
<string name="menu_show_games">Games</string> <string name="menu_show_games">Games</string>
<string name="menu_show_modules">Modules</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="failed_to_save_scope_list">Failed save scope list</string>
<string name="app_description">%1$s\nVersion %2$s</string> <string name="app_description">%1$s\nVersion %2$s</string>
<string name="use_recommended">Recommended</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_required">Reboot is required for apply changes</string>
<string name="reboot">Reboot</string> <string name="reboot">Reboot</string>
<string name="menu_hide">Hide</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 --> <!-- ModulesActivity and AppListActivity -->
<string name="modules_other_app">View in other app</string> <string name="modules_other_app">View in other app</string>

2
core/.gitignore vendored
View File

@ -1,3 +1,3 @@
/build /build
/release /release
/src/main/cpp/main/src/config.cpp /src/main/cpp/main/api/config.cpp

View File

@ -36,7 +36,7 @@ plugins {
} }
val moduleName = "LSPosed" val moduleName = "LSPosed"
val moduleId = "riru_lsposed" val moduleBaseId = "lsposed"
val authors = "LSPosed Developers" val authors = "LSPosed Developers"
val riruModuleId = "lsposed" val riruModuleId = "lsposed"
@ -62,30 +62,13 @@ val androidCompileNdkVersion: String by rootProject.extra
val androidSourceCompatibility: JavaVersion by rootProject.extra val androidSourceCompatibility: JavaVersion by rootProject.extra
val androidTargetCompatibility: 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 { android {
compileSdk = androidCompileSdkVersion compileSdk = androidCompileSdkVersion
ndkVersion = androidCompileNdkVersion ndkVersion = androidCompileNdkVersion
buildToolsVersion = androidBuildToolsVersion buildToolsVersion = androidBuildToolsVersion
flavorDimensions += "api"
buildFeatures { buildFeatures {
prefab = true prefab = true
} }
@ -100,9 +83,9 @@ android {
externalNativeBuild { externalNativeBuild {
ndkBuild { ndkBuild {
arguments += "RIRU_MODULE_API_VERSION=$moduleMaxRiruApiVersion"
arguments += "MODULE_NAME=$riruModuleId"
arguments += "INJECTED_AID=$injectedPackageUid" arguments += "INJECTED_AID=$injectedPackageUid"
arguments += "VERSION_CODE=$verCode"
arguments += "VERSION_NAME=$verName"
arguments += "-j${Runtime.getRuntime().availableProcessors()}" arguments += "-j${Runtime.getRuntime().availableProcessors()}"
} }
} }
@ -139,6 +122,59 @@ android {
sourceCompatibility(androidSourceCompatibility) 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 -> androidComponents.onVariants { v ->
@ -147,19 +183,23 @@ androidComponents.onVariants { v ->
else (v as AnalyticsEnabledApplicationVariant).delegate as ApplicationVariantImpl else (v as AnalyticsEnabledApplicationVariant).delegate as ApplicationVariantImpl
val variantCapped = variant.name.capitalize() val variantCapped = variant.name.capitalize()
val variantLowered = variant.name.toLowerCase() 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" val magiskDir = "$buildDir/magisk/$variantLowered"
task("generateApp${variantCapped}RFile", Jar::class) { task("generateApp${variantCapped}RFile", Jar::class) {
dependsOn(":app:process${variantCapped}Resources") dependsOn(":app:process${buildTypeCapped}Resources")
doLast { doLast {
val rFile = JarFile( val rFile = JarFile(
File( File(
project(":app").buildDir, 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()) { for (entry in rFile.entries()) {
if (entry.name.startsWith("org/lsposed/manager")) { if (entry.name.startsWith("org/lsposed/manager")) {
it.putNextEntry(entry) it.putNextEntry(entry)
@ -176,10 +216,10 @@ androidComponents.onVariants { v ->
val outSrcDir = file("$buildDir/generated/source/signInfo/${variantLowered}") val outSrcDir = file("$buildDir/generated/source/signInfo/${variantLowered}")
val outSrc = file("$outSrcDir/org/lsposed/lspd/util/SignInfo.java") val outSrc = file("$outSrcDir/org/lsposed/lspd/util/SignInfo.java")
val signInfoTask = tasks.register("generate${variantCapped}SignInfo") { val signInfoTask = tasks.register("generate${variantCapped}SignInfo") {
dependsOn(":app:validateSigning${variantCapped}") dependsOn(":app:validateSigning${buildTypeCapped}")
outputs.file(outSrc) outputs.file(outSrc)
doLast { doLast {
val sign = app.buildTypes.named(variantLowered).get().signingConfig val sign = app.buildTypes.named(buildTypeLowered).get().signingConfig
outSrc.parentFile.mkdirs() outSrc.parentFile.mkdirs()
val certificateInfo = KeystoreHelper.getCertificateInfo( val certificateInfo = KeystoreHelper.getCertificateInfo(
sign?.storeType, sign?.storeType,
@ -202,13 +242,16 @@ androidComponents.onVariants { v ->
variant.variantData.registerJavaGeneratingTask(signInfoTask, arrayListOf(outSrcDir)) 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) { val prepareMagiskFilesTask = task("prepareMagiskFiles$variantCapped", Sync::class) {
dependsOn("assemble$variantCapped") dependsOn("assemble$variantCapped")
dependsOn(":app:assemble$variantCapped") dependsOn(":app:assemble$buildTypeCapped")
into(magiskDir) into(magiskDir)
from("${rootProject.projectDir}/README.md") from("${rootProject.projectDir}/README.md")
from("$projectDir/magisk_module") { from("$projectDir/magisk_module") {
exclude("riru.sh", "module.prop") exclude("riru.sh", "module.prop", "customize.sh")
} }
from("$projectDir/magisk_module") { from("$projectDir/magisk_module") {
include("module.prop") include("module.prop")
@ -217,32 +260,45 @@ androidComponents.onVariants { v ->
"versionName" to "v$verName", "versionName" to "v$verName",
"versionCode" to verCode, "versionCode" to verCode,
"authorList" to authors, "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")) filter<FixCrLfFilter>("eol" to FixCrLfFilter.CrLf.newInstance("lf"))
} }
from("${projectDir}/magisk_module") { from("$projectDir/magisk_module") {
include("riru.sh") include("customize.sh")
val tokens = mapOf( val tokens = mapOf("FLAVOR" to flavorLowered)
"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",
)
filter<ReplaceTokens>("tokens" to tokens) filter<ReplaceTokens>("tokens" to tokens)
filter<FixCrLfFilter>("eol" to FixCrLfFilter.CrLf.newInstance("lf")) 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") include("*.apk")
rename(".*\\.apk", "manager.apk") rename(".*\\.apk", "manager.apk")
} }
into("lib") { 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") val dexOutPath = if (buildTypeLowered == "release")
"$buildDir/intermediates/dex/$variantLowered/minify${variantCapped}WithR8" else "$buildDir/intermediates/dex/$variantCapped/minify${variantCapped}WithR8" else
"$buildDir/intermediates/dex/$variantLowered/mergeDex$variantCapped" "$buildDir/intermediates/dex/$variantCapped/mergeDex$variantCapped"
into("framework") { into("framework") {
from(dexOutPath) from(dexOutPath)
rename("classes.dex", "lspd.dex") rename("classes.dex", "lspd.dex")
@ -266,6 +322,8 @@ androidComponents.onVariants { v ->
from(magiskDir) from(magiskDir)
} }
zipAll.dependsOn(zipTask)
val adb: String = androidComponents.sdkComponents.adb.get().asFile.absolutePath val adb: String = androidComponents.sdkComponents.adb.get().asFile.absolutePath
val pushTask = task("push${variantCapped}", Exec::class) { val pushTask = task("push${variantCapped}", Exec::class) {
dependsOn(zipTask) dependsOn(zipTask)
@ -313,7 +371,7 @@ val reRunLspd = task("reRunLspd", Exec::class) {
dependsOn(pushLspd) dependsOn(pushLspd)
dependsOn(pushLspdNative) dependsOn(pushLspdNative)
dependsOn(killLspd) 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 isIgnoreExitValue = true
} }
val tmpApk = "/data/local/tmp/lsp.apk" val tmpApk = "/data/local/tmp/lsp.apk"
@ -335,13 +393,3 @@ task("reRunApp", Exec::class) {
isIgnoreExitValue = true isIgnoreExitValue = true
finalizedBy(reRunLspd) 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)

View File

@ -21,6 +21,20 @@
# shellcheck disable=SC2034 # shellcheck disable=SC2034
SKIPUNZIP=1 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") VERSION=$(grep_prop version "${TMPDIR}/module.prop")
ui_print "- LSPosed version ${VERSION}" ui_print "- LSPosed version ${VERSION}"
@ -44,12 +58,14 @@ check_android_version
check_magisk_version check_magisk_version
check_incompatible_module check_incompatible_module
# Extract riru.sh if [ "$FLAVOR" == "riru" ]; then
extract "$ZIPFILE" 'riru.sh' "$TMPDIR" # Extract riru.sh
. "$TMPDIR/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 enforce_install_from_magisk_app
# Check architecture # Check architecture
@ -73,48 +89,63 @@ extract "$ZIPFILE" 'lspd' "$MODPATH"
rm -f /data/adb/lspd/manager.apk rm -f /data/adb/lspd/manager.apk
extract "$ZIPFILE" 'manager.apk' '/data/adb/lspd' extract "$ZIPFILE" 'manager.apk' '/data/adb/lspd'
mkdir "$MODPATH/riru" ui_print "- Extracting daemon libraries"
mkdir "$MODPATH/riru/lib" if [ "$ARCH" = "arm" ] ; then
mkdir "$MODPATH/riru/lib64" extract "$ZIPFILE" 'lib/armeabi-v7a/libdaemon.so' "$MODPATH" true
elif [ "$ARCH" = "arm64" ]; then
if [ "$ARCH" = "arm" ] || [ "$ARCH" = "arm64" ]; then extract "$ZIPFILE" 'lib/arm64-v8a/libdaemon.so' "$MODPATH" true
ui_print "- Extracting arm libraries" elif [ "$ARCH" = "x86" ]; then
extract "$ZIPFILE" "lib/armeabi-v7a/lib$RIRU_MODULE_LIB_NAME.so" "$MODPATH/riru/lib" true extract "$ZIPFILE" 'lib/x86/libdaemon.so' "$MODPATH" true
elif [ "$ARCH" = "x64" ]; then
if [ "$IS64BIT" = true ]; then extract "$ZIPFILE" 'lib/x86_64/libdaemon.so' "$MODPATH" true
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
fi 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 if [ "$IS64BIT" = true ]; then
ui_print "- Extracting x86 libraries" ui_print "- Extracting arm64 libraries"
extract "$ZIPFILE" "lib/x86/lib$RIRU_MODULE_LIB_NAME.so" "$MODPATH/riru/lib" true extract "$ZIPFILE" "lib/arm64-v8a/lib$RIRU_MODULE_LIB_NAME.so" "$MODPATH/riru/lib64" true
fi
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
fi fi
fi
if [ "$RIRU_MODULE_DEBUG" = true ]; then if [ "$ARCH" = "x86" ] || [ "$ARCH" = "x64" ]; then
mv "$MODPATH/riru" "$MODPATH/system" ui_print "- Extracting x86 libraries"
mv "$MODPATH/system/lib/lib$RIRU_MODULE_LIB_NAME.so" "$MODPATH/system/lib/libriru_$RIRU_MODULE_LIB_NAME.so" extract "$ZIPFILE" "lib/x86/lib$RIRU_MODULE_LIB_NAME.so" "$MODPATH/riru/lib" true
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 [ "$IS64BIT" = true ]; then
if [ "$RIRU_API" -ge 26 ]; then ui_print "- Extracting x64 libraries"
mkdir -p "$MODPATH/riru/lib" extract "$ZIPFILE" "lib/x86_64/lib$RIRU_MODULE_LIB_NAME.so" "$MODPATH/riru/lib64" true
mkdir -p "$MODPATH/riru/lib64" fi
touch "$MODPATH/riru/lib/libriru_$RIRU_MODULE_LIB_NAME" fi
touch "$MODPATH/riru/lib64/libriru_$RIRU_MODULE_LIB_NAME"
else if [ "$RIRU_MODULE_DEBUG" = true ]; then
mkdir -p "/data/adb/riru/modules/$RIRU_MODULE_LIB_NAME" 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
fi fi

View File

@ -1,6 +1,6 @@
id=${moduleId} id=${moduleId}
name=Riru - LSPosed name=${api} - LSPosed
version=${versionName} (${versionCode}) version=${versionName} (${versionCode})
versionCode=${versionCode} versionCode=${versionCode}
author=${authorList} 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}.

View File

@ -1 +1,4 @@
allow system_server system_server process execmem 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

View File

@ -7,10 +7,23 @@ include $(CLEAR_VARS)
LOCAL_MODULE := lspd LOCAL_MODULE := lspd
LOCAL_C_INCLUDES := $(LOCAL_PATH)/include $(LOCAL_PATH)/src LOCAL_C_INCLUDES := $(LOCAL_PATH)/include $(LOCAL_PATH)/src
FILE_LIST := $(filter %.cpp, $(call walk, $(LOCAL_PATH)/src)) FILE_LIST := $(filter %.cpp, $(call walk, $(LOCAL_PATH)/src))
LOCAL_SRC_FILES := $(FILE_LIST:$(LOCAL_PATH)/%=%) LOCAL_SRC_FILES := $(FILE_LIST:$(LOCAL_PATH)/%=%) api/config.cpp
LOCAL_STATIC_LIBRARIES := cxx yahfa riru dobby dex_builder LOCAL_STATIC_LIBRARIES := cxx riru yahfa dobby dex_builder
LOCAL_CFLAGS := -DRIRU_MODULE -DRIRU_MODULE_API_VERSION=${RIRU_MODULE_API_VERSION} ifeq ($(API), riru)
LOCAL_CFLAGS += -DMODULE_NAME=${MODULE_NAME} 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_CFLAGS += -DINJECTED_AID=${INJECTED_AID}
LOCAL_LDLIBS := -llog LOCAL_LDLIBS := -llog
include $(BUILD_SHARED_LIBRARY) 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: ;

View File

@ -25,59 +25,51 @@
#include "logging.h" #include "logging.h"
#include "config.h" #include "config.h"
#include "context.h" #include "context.h"
#define RIRU_MODULE
#include <riru.h> #include <riru.h>
#include "symbol_cache.h"
namespace lspd { namespace lspd {
int *allowUnload = nullptr; int *allowUnload = nullptr;
static constexpr uid_t kAidInjected = INJECTED_AID;
static constexpr uid_t kAidInet = 3003;
namespace { namespace {
std::string magiskPath; std::string magiskPath;
jstring nice_name = nullptr;
jstring app_data_dir = nullptr;
void onModuleLoaded() { void onModuleLoaded() {
LOGI("onModuleLoaded: welcome to LSPosed!"); LOGI("onModuleLoaded: welcome to LSPosed!");
LOGI("onModuleLoaded: version v%s (%d)", versionName, versionCode); LOGI("onModuleLoaded: version v%s (%d)", versionName, versionCode);
Context::GetInstance()->Init();
if constexpr (isDebug) { if constexpr (isDebug) {
Context::GetInstance()->PreLoadDex("/system/" + kDexPath); Context::GetInstance()->PreLoadDex("/system/" + kDexPath);
} else { } else {
Context::GetInstance()->PreLoadDex(magiskPath + '/' + kDexPath); Context::GetInstance()->PreLoadDex(magiskPath + '/' + kDexPath);
} }
InitSymbolCache();
} }
void nativeForkAndSpecializePre(JNIEnv *env, jclass, jint *_uid, jint *, void nativeForkAndSpecializePre(JNIEnv *env, jclass, jint *_uid, jint *,
jintArray *gids, jint *, jintArray *gids, jint *,
jobjectArray *, jint *, jobjectArray *, jint *,
jstring *, jstring *nice_name, jstring *, jstring *_nice_name,
jintArray *, jintArray *, jintArray *, jintArray *,
jboolean *start_child_zygote, jstring *, jboolean *start_child_zygote, jstring *,
jstring *app_data_dir, jboolean *, jstring *_app_data_dir, jboolean *,
jobjectArray *, jobjectArray *,
jobjectArray *, jobjectArray *,
jboolean *, jboolean *,
jboolean *) { jboolean *) {
if (*_uid == kAidInjected) { nice_name = *_nice_name;
int array_size = *gids ? env->GetArrayLength(*gids) : 0; app_data_dir = *_app_data_dir;
auto region = std::make_unique<jint[]>(array_size + 1); Context::GetInstance()->OnNativeForkAndSpecializePre(env, *_uid, *gids,
auto *new_gids = env->NewIntArray(array_size + 1); nice_name,
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,
*start_child_zygote, *start_child_zygote,
*app_data_dir); app_data_dir);
} }
void nativeForkAndSpecializePost(JNIEnv *env, jclass, jint res) { void nativeForkAndSpecializePost(JNIEnv *env, jclass, jint res) {
if (res == 0) 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 *, void nativeForkSystemServerPre(JNIEnv *env, jclass, uid_t *, gid_t *,
@ -88,43 +80,36 @@ namespace lspd {
} }
void nativeForkSystemServerPost(JNIEnv *env, jclass, jint res) { void nativeForkSystemServerPost(JNIEnv *env, jclass, jint res) {
Context::GetInstance()->OnNativeForkSystemServerPost(env, res); if (res == 0)
Context::GetInstance()->OnNativeForkSystemServerPost(env);
} }
/* method added in Android Q */ /* method added in Android Q */
void specializeAppProcessPre(JNIEnv *env, jclass, jint *_uid, jint *, void specializeAppProcessPre(JNIEnv *env, jclass, jint *_uid, jint *,
jintArray *gids, jint *, jintArray *gids, jint *,
jobjectArray *, jint *, jobjectArray *, jint *,
jstring *, jstring *nice_name, jstring *, jstring *_nice_name,
jboolean *start_child_zygote, jstring *, jboolean *start_child_zygote, jstring *,
jstring *app_data_dir, jboolean *, jstring *_app_data_dir, jboolean *,
jobjectArray *, jobjectArray *,
jobjectArray *, jobjectArray *,
jboolean *, jboolean *,
jboolean *) { jboolean *) {
if (*_uid == kAidInjected) { nice_name = *_nice_name;
int array_size = *gids ? env->GetArrayLength(*gids) : 0; app_data_dir = *_app_data_dir;
auto region = std::make_unique<jint[]>(array_size + 1); Context::GetInstance()->OnNativeForkAndSpecializePre(env, *_uid, *gids,
auto *new_gids = env->NewIntArray(array_size + 1); nice_name,
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,
*start_child_zygote, *start_child_zygote,
*app_data_dir); app_data_dir);
} }
void specializeAppProcessPost(JNIEnv *env, jclass) { void specializeAppProcessPost(JNIEnv *env, jclass) {
Context::GetInstance()->OnNativeForkAndSpecializePost(env); Context::GetInstance()->OnNativeForkAndSpecializePost(env, nice_name, app_data_dir);
} }
} }
RiruVersionedModuleInfo module{ RiruVersionedModuleInfo module{
.moduleApiVersion = RIRU_MODULE_API_VERSION, .moduleApiVersion = apiVersion,
.moduleInfo = RiruModuleInfo{ .moduleInfo = RiruModuleInfo{
.supportHide = !isDebug, .supportHide = !isDebug,
.version = versionCode, .version = versionCode,
@ -140,14 +125,11 @@ namespace lspd {
}; };
} }
#define quote(s) #s
#define str(s) quote(s)
RIRU_EXPORT RiruVersionedModuleInfo *init(Riru *riru) { RIRU_EXPORT RiruVersionedModuleInfo *init(Riru *riru) {
LOGD("using riru %d", riru->riruApiVersion); LOGD("using riru %d", riru->riruApiVersion);
LOGD("module path: %s", riru->magiskModulePath); LOGD("module path: %s", riru->magiskModulePath);
lspd::magiskPath = 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"); LOGE("who am i");
return nullptr; return nullptr;
} }

View File

@ -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);

View File

@ -71,5 +71,7 @@ namespace lspd {
} }
extern const int versionCode; extern const int versionCode;
extern const int apiVersion;
extern const char* const versionName; extern const char* const versionName;
extern const char* const moduleName;
} }

View File

@ -41,6 +41,9 @@ namespace lspd {
constexpr int SHARED_RELRO_UID = 1037; constexpr int SHARED_RELRO_UID = 1037;
constexpr int PER_USER_RANGE = 100000; 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) { void Context::CallOnPostFixupStaticTrampolines(void *class_ptr) {
if (!class_ptr || !class_linker_class_ || !post_fixup_static_mid_) [[unlikely]] { if (!class_ptr || !class_linker_class_ || !post_fixup_static_mid_) [[unlikely]] {
return; return;
@ -55,24 +58,33 @@ namespace lspd {
} }
} }
void Context::PreLoadDex(std::string_view dex_path) { Context::PreloadedDex::PreloadedDex(FILE *f) {
if (!dex.empty()) [[unlikely]] return; 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"); Context::PreloadedDex::~PreloadedDex() {
if (!f) { 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()); LOGE("Fail to open dex from %s", dex_path.data());
return; 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) { void Context::LoadDex(JNIEnv *env) {
@ -88,6 +100,7 @@ namespace lspd {
auto initMid = JNI_GetMethodID(env, in_memory_classloader, "<init>", auto initMid = JNI_GetMethodID(env, in_memory_classloader, "<init>",
"(Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V"); "(Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V");
auto byte_buffer_class = JNI_FindClass(env, "java/nio/ByteBuffer"); auto byte_buffer_class = JNI_FindClass(env, "java/nio/ByteBuffer");
auto dex = std::move(dex_);
auto dex_buffer = env->NewDirectByteBuffer(dex.data(), dex.size()); auto dex_buffer = env->NewDirectByteBuffer(dex.data(), dex.size());
if (auto my_cl = JNI_NewObject(env, in_memory_classloader, initMid, if (auto my_cl = JNI_NewObject(env, in_memory_classloader, initMid,
dex_buffer, sys_classloader)) { dex_buffer, sys_classloader)) {
@ -102,6 +115,10 @@ namespace lspd {
env->GetJavaVM(&vm_); env->GetJavaVM(&vm_);
} }
void Context::Init() {
InitSymbolCache();
}
void Context::Init(JNIEnv *env) { void Context::Init(JNIEnv *env) {
if (auto class_linker_class = FindClassFromCurrentLoader(env, kClassLinkerClassName)) { if (auto class_linker_class = FindClassFromCurrentLoader(env, kClassLinkerClassName)) {
class_linker_class_ = JNI_NewGlobalRef(env, class_linker_class); class_linker_class_ = JNI_NewGlobalRef(env, class_linker_class);
@ -110,7 +127,8 @@ namespace lspd {
"onPostFixupStaticTrampolines", "onPostFixupStaticTrampolines",
"(Ljava/lang/Class;)V"); "(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); entry_class_ = JNI_NewGlobalRef(env, entry_class);
} }
@ -122,13 +140,16 @@ namespace lspd {
} }
ScopedLocalRef<jclass> 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}; 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", static jmethodID mid = JNI_GetMethodID(env, clz, "loadClass",
"(Ljava/lang/String;)Ljava/lang/Class;"); "(Ljava/lang/String;)Ljava/lang/Class;");
if (!mid) { 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]] { if (mid) [[likely]] {
auto target = JNI_CallObjectMethod(env, class_loader, mid, auto target = JNI_CallObjectMethod(env, class_loader, mid,
@ -167,8 +188,7 @@ namespace lspd {
} }
void void
Context::OnNativeForkSystemServerPost(JNIEnv *env, jint res) { Context::OnNativeForkSystemServerPost(JNIEnv *env) {
if (res != 0) return;
if (!skip_) { if (!skip_) {
LoadDex(env); LoadDex(env);
Service::instance()->HookBridge(*this, env); Service::instance()->HookBridge(*this, env);
@ -184,13 +204,22 @@ namespace lspd {
void Context::OnNativeForkAndSpecializePre(JNIEnv *env, void Context::OnNativeForkAndSpecializePre(JNIEnv *env,
jint uid, jint uid,
jintArray &gids,
jstring nice_name, jstring nice_name,
jboolean is_child_zygote, jboolean is_child_zygote,
jstring app_data_dir) { 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); Service::instance()->InitService(env);
const auto app_id = uid % PER_USER_RANGE; const auto app_id = uid % PER_USER_RANGE;
app_data_dir_ = app_data_dir;
nice_name_ = nice_name;
JUTFString process_name(env, nice_name); JUTFString process_name(env, nice_name);
skip_ = !sym_initialized; skip_ = !sym_initialized;
if (!skip_ && !app_data_dir) { if (!skip_ && !app_data_dir) {
@ -213,10 +242,11 @@ namespace lspd {
} }
void void
Context::OnNativeForkAndSpecializePost(JNIEnv *env) { Context::OnNativeForkAndSpecializePost(JNIEnv *env, jstring nice_name,
const JUTFString process_name(env, nice_name_); jstring app_data_dir) {
const JUTFString process_name(env, nice_name);
auto binder = skip_ ? ScopedLocalRef<jobject>{env, nullptr} auto binder = skip_ ? ScopedLocalRef<jobject>{env, nullptr}
: Service::instance()->RequestBinder(env, nice_name_); : Service::instance()->RequestBinder(env, nice_name);
if (binder) { if (binder) {
InstallInlineHooks(); InstallInlineHooks();
LoadDex(env); LoadDex(env);
@ -224,7 +254,7 @@ namespace lspd {
LOGD("Done prepare"); LOGD("Done prepare");
FindAndCall(env, "forkAndSpecializePost", FindAndCall(env, "forkAndSpecializePost",
"(Ljava/lang/String;Ljava/lang/String;Landroid/os/IBinder;)V", "(Ljava/lang/String;Ljava/lang/String;Landroid/os/IBinder;)V",
app_data_dir_, nice_name_, app_data_dir, nice_name,
binder); binder);
LOGD("injected xposed into %s", process_name.get()); LOGD("injected xposed into %s", process_name.get());
setAllowUnload(false); setAllowUnload(false);

View File

@ -45,31 +45,70 @@ namespace lspd {
void CallOnPostFixupStaticTrampolines(void *class_ptr); 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); 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 OnNativeForkSystemServerPre(JNIEnv *env);
void PreLoadDex(std::string_view dex_paths); void PreLoadDex(std::string_view dex_paths);
void Init();
private: private:
inline static std::unique_ptr<Context> instance_ = std::make_unique<Context>(); inline static std::unique_ptr<Context> instance_ = std::make_unique<Context>();
jobject inject_class_loader_ = nullptr; jobject inject_class_loader_ = nullptr;
jclass entry_class_ = nullptr; jclass entry_class_ = nullptr;
jstring app_data_dir_ = nullptr;
jstring nice_name_ = nullptr;
JavaVM *vm_ = nullptr; JavaVM *vm_ = nullptr;
jclass class_linker_class_ = nullptr; jclass class_linker_class_ = nullptr;
jmethodID post_fixup_static_mid_ = nullptr; jmethodID post_fixup_static_mid_ = nullptr;
bool skip_ = false; 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() {} Context() {}
@ -78,11 +117,13 @@ namespace lspd {
void Init(JNIEnv *env); void Init(JNIEnv *env);
static ScopedLocalRef<jclass> FindClassFromLoader(JNIEnv *env, jobject class_loader, static ScopedLocalRef<jclass> FindClassFromLoader(JNIEnv *env, jobject class_loader,
std::string_view class_name); std::string_view class_name);
static void setAllowUnload(bool unload); static void setAllowUnload(bool unload);
template<typename ...Args> 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>(); friend std::unique_ptr<Context> std::make_unique<Context>();
}; };

View File

@ -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);

View File

@ -1,6 +0,0 @@
#include "config.h"
namespace lspd {
const int versionCode = ${VERSION_CODE};
const char* const versionName = "${VERSION_NAME}";
}

View File

@ -50,6 +50,7 @@ public class ConfigFileManager {
private static final Path lockPath = basePath.resolve("lock"); private static final Path lockPath = basePath.resolve("lock");
private static final Path configDirPath = basePath.resolve("config"); private static final Path configDirPath = basePath.resolve("config");
static final File dbPath = configDirPath.resolve("modules_config.db").toFile(); 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 logDirPath = basePath.resolve("log");
private static final Path oldLogDirPath = basePath.resolve("log.old"); private static final Path oldLogDirPath = basePath.resolve("log.old");
private static final DateTimeFormatter formatter = private static final DateTimeFormatter formatter =

View File

@ -459,6 +459,8 @@ public class ConfigManager {
final var obsoleteModules = new HashSet<Application>(); final var obsoleteModules = new HashSet<Application>();
final var moduleAvailability = new HashMap<Pair<String, Integer>, Boolean>(); final var moduleAvailability = new HashMap<Pair<String, Integer>, Boolean>();
final var cachedProcessScope = new HashMap<Pair<String, Integer>, List<ProcessScope>>(); final var cachedProcessScope = new HashMap<Pair<String, Integer>, List<ProcessScope>>();
final var denylist = new HashSet<>(getDenyListPackages());
while (cursor.moveToNext()) { while (cursor.moveToNext()) {
Application app = new Application(); Application app = new Application();
app.packageName = cursor.getString(appPkgNameIdx); app.packageName = cursor.getString(appPkgNameIdx);
@ -488,6 +490,8 @@ public class ConfigManager {
try { try {
List<ProcessScope> processesScope = cachedProcessScope.computeIfAbsent(new Pair<>(app.packageName, app.userId), (k) -> { List<ProcessScope> processesScope = cachedProcessScope.computeIfAbsent(new Pair<>(app.packageName, app.userId), (k) -> {
try { try {
if (denylist.contains(app.packageName))
Log.w(TAG, app.packageName + " is on denylist. It may not take effect.");
return getAssociatedProcesses(app); return getAssociatedProcesses(app);
} catch (RemoteException e) { } catch (RemoteException e) {
return Collections.emptyList(); return Collections.emptyList();
@ -920,4 +924,23 @@ public class ConfigManager {
public boolean isSepolicyLoaded() { public boolean isSepolicyLoaded() {
return sepolicyLoaded; 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;
}
} }

View File

@ -719,6 +719,11 @@ public class LSPManagerService extends ILSPManagerService.Stub {
setAddShortcut(true); setAddShortcut(true);
} }
@Override
public List<String> getDenyListPackages() {
return ConfigManager.getDenyListPackages();
}
@Override @Override
public void flashZip(String zipPath, ParcelFileDescriptor outputStream) { public void flashZip(String zipPath, ParcelFileDescriptor outputStream) {
var processBuilder = new ProcessBuilder("magisk", "--install-module", zipPath); var processBuilder = new ProcessBuilder("magisk", "--install-module", zipPath);

View File

@ -75,4 +75,6 @@ interface ILSPManagerService {
oneway void flashZip(String zipPath, in ParcelFileDescriptor outputStream) = 39; oneway void flashZip(String zipPath, in ParcelFileDescriptor outputStream) = 39;
boolean performDexOptMode(String packageName) = 40; boolean performDexOptMode(String packageName) = 40;
List<String> getDenyListPackages() = 41;
} }