Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions manager/src/main/java/org/lsposed/lspatch/Patcher.kt
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ object Patcher {
add("-m"); add(it)
}
if(injectDex) add("--injectdex")
if (config.useMicroG) add("--useMicroG")
if (!MyKeyStore.useDefault) {
addAll(arrayOf("-k", MyKeyStore.file.path, Configs.keyStorePassword, Configs.keyStoreAlias, Configs.keyStoreAliasPassword))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,14 @@ private fun PatchOptionsBody(modifier: Modifier, onAddEmbed: () -> Unit) {
desc = stringResource(R.string.patch_inject_dex_desc)
)

SettingsCheckBox(
modifier = Modifier.clickable { viewModel.useMicroG = !viewModel.useMicroG },
checked = viewModel.useMicroG,
icon = Icons.Outlined.CloudSync,
title = stringResource(R.string.patch_use_microg),
desc = stringResource(R.string.patch_use_microg_desc)
)

var bypassExpanded by remember { mutableStateOf(false) }
AnywhereDropdown(
expanded = bypassExpanded,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class NewPatchViewModel : ViewModel() {
var overrideVersionCode by mutableStateOf(false)
var sigBypassLevel by mutableStateOf(2)
var injectDex by mutableStateOf(false)
var useMicroG by mutableStateOf(false)
var embeddedModules = emptyList<AppInfo>()

lateinit var patchApp: AppInfo
Expand Down Expand Up @@ -92,7 +93,7 @@ class NewPatchViewModel : ViewModel() {
if (useManager) embeddedModules = emptyList()
patchOptions = Patcher.Options(
injectDex = injectDex,
config = PatchConfig(useManager, debuggable, overrideVersionCode, sigBypassLevel, null, null),
config = PatchConfig(useManager, debuggable, overrideVersionCode, sigBypassLevel, null, null, useMicroG),
apkPaths = listOf(patchApp.app.sourceDir) + (patchApp.app.splitSourceDirs ?: emptyArray()),
embeddedModules = embeddedModules.flatMap { listOf(it.app.sourceDir) + (it.app.splitSourceDirs ?: emptyArray()) }
)
Expand Down
6 changes: 4 additions & 2 deletions manager/src/main/res/values-zh-rCN/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@
<string name="patch_sigbypasslv2">lv2: 绕过 PM + openat (libc)</string>
<string name="patch_override_version_code">覆写版本号</string>
<string name="patch_override_version_code_desc">将修补的 App 版本号重写为 1\n这将允许后续降级安装,并且通常来说这不会影响应用实际感知到的版本号</string>
<string name="patch_inject_dex">注入加载器 Dex</string>
<string name="patch_inject_dex_desc">对那些需要孤立服务进程的应用程序,譬如说浏览器的渲染引擎,请勾选此选项以确保他们正常运行</string>
<string name="patch_use_microg">强制启用 MicroG 支持</string>
<string name="patch_use_microg_desc">重新导向 GMS 请求至社区版 MicroG。适用于 YouTube 等 Google 应用程序,需自行安装对应的 MicroG 服务。</string>
<string name="patch_start">开始修补</string>
<string name="patch_return">返回</string>
<string name="patch_uninstall_text">由于签名不同,安装修补的应用前需要先卸载原应用。\n确保您已备份好个人数据。</string>
Expand All @@ -86,6 +90,4 @@
<string name="settings_keystore_wrong_alias">别名错误</string>
<string name="settings_keystore_wrong_alias_password">别名密码错误</string>
<string name="settings_detail_patch_logs">详细修补日志</string>
<string name="patch_inject_dex">注入加载器 Dex</string>
<string name="patch_inject_dex_desc">对那些需要孤立服务进程的应用程序,譬如说浏览器的渲染引擎,请勾选此选项以确保他们正常运行</string>
</resources>
6 changes: 4 additions & 2 deletions manager/src/main/res/values-zh-rTW/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@
<string name="patch_sigbypasslv2">lv2: 繞過 PM + openat (libc)</string>
<string name="patch_override_version_code">覆蓋版本編號</string>
<string name="patch_override_version_code_desc">將打包應用程式的版本編號改成 1\n允許以後降級安裝,一般來說,這不會影響應用程式實際感知的版本編號。</string>
<string name="patch_inject_dex">注入載入器 Dex</string>
<string name="patch_inject_dex_desc">對那些需要孤立服務程序的應用程式,譬如說瀏覽器的渲染引擎,請勾選此選項以確保他們正常執行</string>
<string name="patch_use_microg">強制啟用 MicroG 支援</string>
<string name="patch_use_microg_desc">重新導向 GMS 請求至社群版 MicroG。適用於 YouTube 等 Google 應用程式,需自行安裝對應的 MicroG 服務。</string>
<string name="patch_start">開始打包</string>
<string name="patch_return">返回</string>
<string name="patch_uninstall_text">由於簽名不同,安裝前需要先解除安裝原程式。\n確保您已備份好個人資料。</string>
Expand All @@ -86,6 +90,4 @@
<string name="settings_keystore_wrong_alias">別名錯誤</string>
<string name="settings_keystore_wrong_alias_password">別名密碼錯誤</string>
<string name="settings_detail_patch_logs">詳細打包日誌</string>
<string name="patch_inject_dex">注入加載器 Dex</string>
<string name="patch_inject_dex_desc">對那些需要孤立服務進程的應用程序,譬如說瀏覽器的渲染引擎,請勾選此選項以確保他們正常運行</string>
</resources>
6 changes: 4 additions & 2 deletions manager/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@
<string name="patch_sigbypasslv2">lv2: Bypass PM + openat (libc)</string>
<string name="patch_override_version_code">Override version code</string>
<string name="patch_override_version_code_desc">Override the patched app\'s version code to 1\nThis allows downgrade installation in the future, and generally this will not affect the version code actually perceived by the application</string>
<string name="patch_inject_dex">Inject loader dex</string>
<string name="patch_inject_dex_desc">For applications with isolated services, such as the render engines of browsers, please turn on this option to ensure that they work properly.</string>
<string name="patch_use_microg">Force enable MicroG support</string>
<string name="patch_use_microg_desc">Redirect GMS requests to the community version of MicroG (such as ReVanced GmsCore). Applicable to Google apps like YouTube, requires manually installing the corresponding MicroG service.</string>
<string name="patch_start">Start Patch</string>
<string name="patch_return">Return</string>
<string name="patch_uninstall_text">Due to different signatures, you need to uninstall the original app before installing the patched one.\nMake sure you have backed up personal data.</string>
Expand All @@ -90,6 +94,4 @@
<string name="settings_keystore_wrong_alias">Wrong alias name</string>
<string name="settings_keystore_wrong_alias_password">Wrong alias password</string>
<string name="settings_detail_patch_logs">Detail patch logs</string>
<string name="patch_inject_dex">Inject loader dex</string>
<string name="patch_inject_dex_desc">For applications with isolated services, such as the render engines of browsers, please turn on this option to ensure that they work properly.</string>
</resources>
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
package org.lsposed.lspatch.loader;

import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.net.Uri;
import android.util.Log;

import org.lsposed.lspatch.share.Constants;

import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;

public class GmsRedirector {
private static final String TAG = "LSPatch-GmsRedirect";
private static final String REAL_GMS = Constants.REAL_GMS_PACKAGE_NAME;

// 鎖定社群主流的 MicroG 套件名稱
private static final String[] MICROG_PACKAGES = {
"app.revanced.android.gms", // ReVanced GmsCore (推薦)
"org.microg.gms", // Original MicroG
};

private static String targetGms = null;
private static String originalSignature;

public static void activate(Context context, String origSig) {
originalSignature = origSig;

targetGms = findInstalledMicroG(context);
if (targetGms == null) {
Log.w(TAG, "No MicroG/GmsCore found! GMS redirect disabled.");
return;
}

Log.i(TAG, "Activating GMS redirect: " + REAL_GMS + " -> " + targetGms);

hookIntentSetPackage();
hookIntentSetComponent();
hookIntentResolve();
hookContentResolverAcquire();
hookPackageManagerGetPackageInfo(context);

Log.i(TAG, "GMS redirect hooks installed");
}

private static String findInstalledMicroG(Context context) {
PackageManager pm = context.getPackageManager();
for (String pkg : MICROG_PACKAGES) {
try {
pm.getPackageInfo(pkg, 0);
return pkg;
} catch (PackageManager.NameNotFoundException ignored) {}
}
return null;
}

private static String redirectPackage(String pkg) {
if (REAL_GMS.equals(pkg) || "com.google.android.gsf".equals(pkg)) {
return targetGms;
}
return null;
}

private static String redirectAuthority(String authority) {
if (authority == null) return null;
if (authority.startsWith(REAL_GMS + ".")) {
return targetGms + authority.substring(REAL_GMS.length());
}
if (authority.equals(REAL_GMS)) {
return targetGms;
}
if (authority.startsWith("com.google.android.gsf")) {
return authority.replace("com.google.android.gsf", targetGms);
}
return null;
}

private static void hookIntentSetPackage() {
try {
XposedBridge.hookAllMethods(Intent.class, "setPackage", new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
String pkg = (String) param.args[0];
String redirected = redirectPackage(pkg);
if (redirected != null) param.args[0] = redirected;
}
});
} catch (Throwable t) {
Log.e(TAG, "Failed to hook Intent.setPackage", t);
}
}

private static void hookIntentSetComponent() {
try {
XposedBridge.hookAllMethods(Intent.class, "setComponent", new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
ComponentName cn = (ComponentName) param.args[0];
if (cn != null) {
String redirected = redirectPackage(cn.getPackageName());
if (redirected != null) {
param.args[0] = new ComponentName(redirected, cn.getClassName());
}
}
}
});
} catch (Throwable t) {
Log.e(TAG, "Failed to hook Intent.setComponent", t);
}
}

private static void hookIntentResolve() {
try {
XposedBridge.hookAllConstructors(Intent.class, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
Intent intent = (Intent) param.thisObject;
ComponentName cn = intent.getComponent();
if (cn != null) {
String redirected = redirectPackage(cn.getPackageName());
if (redirected != null) {
intent.setComponent(new ComponentName(redirected, cn.getClassName()));
}
}
String pkg = intent.getPackage();
if (pkg != null) {
String redirected = redirectPackage(pkg);
if (redirected != null) {
intent.setPackage(redirected);
}
}
}
});
} catch (Throwable t) {
Log.e(TAG, "Failed to hook Intent constructors", t);
}
}

private static void hookContentResolverAcquire() {
try {
for (String method : new String[]{
"acquireProvider", "acquireContentProviderClient",
"acquireUnstableProvider", "acquireUnstableContentProviderClient"
}) {
try {
XposedBridge.hookAllMethods(ContentResolver.class, method, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
if (param.args[0] instanceof Uri) {
Uri uri = (Uri) param.args[0];
String newAuth = redirectAuthority(uri.getAuthority());
if (newAuth != null) {
param.args[0] = uri.buildUpon().authority(newAuth).build();
}
} else if (param.args[0] instanceof String) {
String newAuth = redirectAuthority((String) param.args[0]);
if (newAuth != null) {
param.args[0] = newAuth;
}
}
}
});
} catch (Throwable ignored) {}
}

// 攔截 ContentResolver.call,遇到 SecurityException 則自動重試
try {
XposedBridge.hookAllMethods(ContentResolver.class, "call", new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
for (int i = 0; i < param.args.length; i++) {
if (param.args[i] instanceof Uri) {
Uri uri = (Uri) param.args[i];
String newAuth = redirectAuthority(uri.getAuthority());
if (newAuth != null) {
param.args[i] = uri.buildUpon().authority(newAuth).build();
}
} else if (param.args[i] instanceof String && i == 0) {
String newAuth = redirectAuthority((String) param.args[i]);
if (newAuth != null) {
param.args[i] = newAuth;
}
}
}
}

@Override
protected void afterHookedMethod(MethodHookParam param) {
if (param.getThrowable() instanceof SecurityException) {
String msg = param.getThrowable().getMessage();
if (msg != null && (msg.contains("GoogleCertificatesRslt") ||
msg.contains("not allowed") ||
msg.contains("Access denied"))) {
Log.i(TAG, "GMS rejected call, retrying with MicroG");
for (int i = 0; i < param.args.length; i++) {
if (param.args[i] instanceof Uri) {
Uri uri = (Uri) param.args[i];
String authority = uri.getAuthority();
if (authority != null && authority.contains(REAL_GMS)) {
param.args[i] = uri.buildUpon()
.authority(authority.replace(REAL_GMS, targetGms))
.build();
}
} else if (param.args[i] instanceof String && i == 0) {
String s = (String) param.args[i];
if (s.contains(REAL_GMS)) {
param.args[i] = s.replace(REAL_GMS, targetGms);
}
}
}
param.setThrowable(null);
param.setResult(null);
}
}
}
});
} catch (Throwable ignored) {}
} catch (Throwable t) {
Log.e(TAG, "Failed to hook ContentResolver", t);
}
}

private static void hookPackageManagerGetPackageInfo(Context context) {
try {
XposedHelpers.findAndHookMethod(
context.getPackageManager().getClass(),
"getPackageInfo",
String.class, int.class,
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
PackageInfo pi = (PackageInfo) param.getResult();
if (pi != null && targetGms != null) {
if (targetGms.equals(pi.packageName) && (((int) param.args[1]) & PackageManager.GET_SIGNATURES) != 0) {
if (originalSignature != null && !originalSignature.isEmpty()) {
try {
byte[] sigBytes = android.util.Base64.decode(originalSignature, android.util.Base64.DEFAULT);
pi.signatures = new Signature[]{new Signature(sigBytes)};
} catch (Throwable ignored) {}
}
}
}
}
}
);
} catch (Throwable t) {
Log.e(TAG, "Failed to hook PackageManager.getPackageInfo", t);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,11 @@ public static void onLoad() throws RemoteException, IOException {
switchAllClassLoader();
SigBypass.doSigBypass(context, config.optInt("sigBypassLevel"));

if (config.optBoolean("useMicroG")) {
String originalSignature = config.optString("originalSignature");
GmsRedirector.activate(context, originalSignature);
}

Log.i(TAG, "LSPatch bootstrap completed");
}

Expand Down
Loading