feat: 引入輕量級 MicroG 支援與 GMS 請求重導向機制
為 NPatch 引入了原生的 MicroG 整合支援,允許修補後的 Google 應用程式(例如 YouTube)在依賴 Google Play 服務的環境下,透過社群版 MicroG(如 ReVanced GmsCore)正常運作。
詳細改動範圍包含:
* ** UI Manager **
- 於 `NewPatchScreen` 的修補選項清單中,新增「啟用 MicroG 支援」核取方塊與相容性說明。
- 更新 `NewPatchViewModel`,透過 `Patcher.kt` 將使用者的 `useMicroG` 選擇狀態以 `--useMicroG` 指令列參數的形式,傳遞給底層修補引擎。
* ** Patcher **
- 於 `NPatch.java` 中新增對 `--useMicroG` 參數的解析邏輯。
- 擴充 `modifyManifestFile` 邏輯:當啟用 MicroG 支援時,自動讀取目標應用的原始簽名並轉碼為 Hex 格式。
- 於 `AndroidManifest.xml` 中動態注入 `fake-signature` 的 `<meta-data>` 節點與 `android.permission.FAKE_PACKAGE_SIGNATURE` 權限,以滿足 MicroG 進行簽名欺騙(Signature Spoofing)時的驗證需求。
* ** Patch Loader **
- 實作 `GmsRedirector.java` 類別,負責在應用程式執行期間動態攔截並重導向 IPC 通訊:
1. 掛鉤 `Intent.setPackage`、`Intent.setComponent` 與建構子,將所有指向 `com.google.android.gms` 與 GSF 的意圖,強制導向至設備上已安裝的社群版 MicroG 套件(優先支援 `app.revanced.android.gms` 與 `org.microg.gms`)。
2. 掛鉤 `ContentResolver`,替換帶有 GMS Authority 的 URI。針對真實 GMS 拋出的 `SecurityException`(憑證拒絕存取)實作了智慧攔截與重試機制。
3. 掛鉤 `PackageManager.getPackageInfo` 實現動態簽名偽裝,使修補後的應用程式將 MicroG 視為官方正版 GMS。
- 於 `LSPApplication.java` 的 `onLoad` 階段,依據 `PatchConfig` 設定檔動態喚醒並初始化 `GmsRedirector`。
* ** Share **
- 更新 `PatchConfig.java` 以儲存與傳遞 `useMicroG` 布林值狀態。
- 於 `Constants.java` 中定義 `REAL_GMS_PACKAGE_NAME` 常數,供各模組統一呼叫。
Co-Authored-By: MrZhongzq <108169409+MrZhongzq@users.noreply.github.com>
This commit is contained in:
parent
01d344ed01
commit
e640da4f57
|
|
@ -36,6 +36,7 @@ object Patcher {
|
|||
}
|
||||
if (config.injectProvider) add("--provider")
|
||||
if(injectDex) add("--injectdex")
|
||||
if (config.useMicroG) add("--useMicroG")
|
||||
if (!MyKeyStore.useDefault) {
|
||||
addAll(arrayOf("-k", MyKeyStore.file.path, Configs.keyStorePassword, Configs.keyStoreAlias, Configs.keyStoreAliasPassword))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -365,6 +365,13 @@ private fun PatchOptionsBody(modifier: Modifier, onAddEmbed: () -> Unit) {
|
|||
title = stringResource(R.string.patch_inject_mt_provider),
|
||||
desc = stringResource(R.string.patch_inject_mt_provider_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)
|
||||
)
|
||||
SettingsCheckBox(
|
||||
modifier = Modifier.clickable { viewModel.outputLog = !viewModel.outputLog },
|
||||
checked = viewModel.outputLog,
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ class NewPatchViewModel : ViewModel() {
|
|||
var injectDex by mutableStateOf(false)
|
||||
var injectProvider by mutableStateOf(false)
|
||||
var outputLog by mutableStateOf(true)
|
||||
var useMicroG by mutableStateOf(false)
|
||||
var embeddedModules = emptyList<AppInfo>()
|
||||
|
||||
lateinit var patchApp: AppInfo
|
||||
|
|
@ -99,7 +100,7 @@ class NewPatchViewModel : ViewModel() {
|
|||
private fun submitPatch() {
|
||||
Log.d(TAG, "Submit Patch")
|
||||
if (useManager) embeddedModules = emptyList()
|
||||
val config = PatchConfig(useManager, debuggable, overrideVersionCode, sigBypassLevel, null, null, injectProvider, outputLog, newPackageName)
|
||||
val config = PatchConfig(useManager, debuggable, overrideVersionCode, sigBypassLevel, null, null, injectProvider, outputLog, newPackageName, useMicroG)
|
||||
patchOptions = Patcher.Options(
|
||||
newPackageName = newPackageName,
|
||||
injectDex = injectDex,
|
||||
|
|
|
|||
|
|
@ -73,7 +73,8 @@
|
|||
<string name="patch_inject_mt_provider_desc">注入文件提供器以在没有 Root 权限的情况下管理 data 目录的文件 (来自 MT 管理器)</string>
|
||||
<string name="patch_inject_dex">注入加载器 Dex</string>
|
||||
<string name="patch_inject_dex_desc">对那些需要孤立服务进程的应用程序,譬如说浏览器的渲染引擎,请勾选此选项以确保他们正常运行</string>
|
||||
<string name="patch_output_log_to_media">日志输出到 Media 目录</string>
|
||||
<string name="patch_use_microg">强制启用 MicroG 支持</string>
|
||||
<string name="patch_use_microg_desc">重新导向 GMS 请求至社区版 MicroG。适用于 YouTube 等 Google 应用程序,需自行安装对应的 MicroG 服务。</string> <string name="patch_output_log_to_media">日志输出到 Media 目录</string>
|
||||
<string name="patch_output_log_to_media_desc">将模块的 Xposed 日志输出到目标应用的 Media 目录</string>
|
||||
<string name="patch_start">开始修补</string>
|
||||
<string name="patch_return">返回</string>
|
||||
|
|
|
|||
|
|
@ -73,6 +73,8 @@
|
|||
<string name="patch_inject_mt_provider_desc">注入檔案选取器以在沒有 Root 權限的情況下管理 data 目錄的檔案(來自 MT 管理器)</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_output_log_to_media">日誌輸出到 Media 目錄</string>
|
||||
<string name="patch_output_log_to_media_desc">將模組的 Xposed 日誌輸出到目標應用的 Media 目錄</string>
|
||||
<string name="patch_start">開始打包</string>
|
||||
|
|
|
|||
|
|
@ -75,6 +75,8 @@
|
|||
<string name="patch_inject_mt_provider_desc">Inject file providers to manage files in the data directory without root privileges (From MT Manager)</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_output_log_to_media">Output Log to Media Directory</string>
|
||||
<string name="patch_output_log_to_media_desc">Output the Xposed log to the target application Media directory.</string>
|
||||
<string name="patch_start">Start Patch</string>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,256 @@
|
|||
package org.lsposed.npatch.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.npatch.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 = "NPatch-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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -145,6 +145,11 @@ public class LSPApplication {
|
|||
switchAllClassLoader();
|
||||
SigBypass.doSigBypass(context, config.sigBypassLevel);
|
||||
|
||||
if (config.useMicroG) {
|
||||
Log.i(TAG, "Activating MicroG redirect via NPatch");
|
||||
GmsRedirector.activate(context, config.originalSignature);
|
||||
}
|
||||
|
||||
Log.i(TAG, "NPatch bootstrap completed");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -89,6 +89,12 @@ public class NPatch {
|
|||
@Parameter(names = {"--provider"}, description = "Inject Provider to manager data files")
|
||||
private boolean isInjectProvider = false;
|
||||
|
||||
@Parameter(names = {"--installerSource"}, description = "Original app installer source")
|
||||
private String installerSource = "";
|
||||
|
||||
@Parameter(names = {"--useMicroG"}, description = "Redirect GMS calls to community MicroG")
|
||||
private boolean useMicroG = false;
|
||||
|
||||
@Parameter(names = {"--outputLog"}, description = "Output Log to Media")
|
||||
private boolean outputLog = true;
|
||||
|
||||
|
|
@ -287,10 +293,10 @@ public class NPatch {
|
|||
|
||||
logger.i("Patching apk...");
|
||||
// modify manifest
|
||||
final var config = new PatchConfig(useManager, debuggableFlag, overrideVersionCode, sigbypassLevel, originalSignature, appComponentFactory, isInjectProvider, outputLog, newPackage);
|
||||
final var config = new PatchConfig(useManager, debuggableFlag, overrideVersionCode, sigbypassLevel, originalSignature, appComponentFactory, isInjectProvider, outputLog, newPackage, useMicroG);
|
||||
final var configBytes = new Gson().toJson(config).getBytes(StandardCharsets.UTF_8);
|
||||
final var metadata = Base64.getEncoder().encodeToString(configBytes);
|
||||
try (var is = new ByteArrayInputStream(modifyManifestFile(manifestEntry.open(), metadata, minSdkVersion, pair.packageName, newPackage))) {
|
||||
try (var is = new ByteArrayInputStream(modifyManifestFile(manifestEntry.open(), metadata, minSdkVersion, pair.packageName, newPackage, originalSignature))) {
|
||||
dstZFile.add(ANDROID_MANIFEST_XML, is);
|
||||
} catch (Throwable e) {
|
||||
throw new PatchError("Error when modifying manifest", e);
|
||||
|
|
@ -422,44 +428,52 @@ public class NPatch {
|
|||
}
|
||||
}
|
||||
|
||||
private byte[] modifyManifestFile(InputStream is, String metadata, int minSdkVersion, String originPackage, String newPackage) throws IOException {
|
||||
private byte[] modifyManifestFile(InputStream is, String metadata, int minSdkVersion, String originPackage, String newPackage, String originalSignature) throws IOException {
|
||||
ModificationProperty property = new ModificationProperty();
|
||||
|
||||
String targetPackage = (newPackage != null && !newPackage.isEmpty()) ? newPackage : originPackage;
|
||||
|
||||
if (overrideVersionCode)
|
||||
property.addManifestAttribute(new AttributeItem(NodeValue.Manifest.VERSION_CODE, 1));
|
||||
if (minSdkVersion < 28)
|
||||
if (minSdkVersion > 0)
|
||||
property.addUsesSdkAttribute(new AttributeItem(NodeValue.UsesSDK.MIN_SDK_VERSION, minSdkVersion));
|
||||
else
|
||||
property.addUsesSdkAttribute(new AttributeItem(NodeValue.UsesSDK.MIN_SDK_VERSION, 27));
|
||||
property.addApplicationAttribute(new AttributeItem(NodeValue.Application.DEBUGGABLE, debuggableFlag));
|
||||
property.addApplicationAttribute(new AttributeItem("appComponentFactory", PROXY_APP_COMPONENT_FACTORY));
|
||||
property.addApplicationAttribute(new AttributeItem("isSplitRequired", false));
|
||||
|
||||
if (!targetPackage.equals(originPackage)) {
|
||||
property.addManifestAttribute(new AttributeItem(NodeValue.Manifest.PACKAGE, targetPackage).setNamespace(null));
|
||||
}
|
||||
property.setPermissionMapper((type, permission) -> {
|
||||
if (permission.startsWith(originPackage)) {
|
||||
return permission.replaceFirst(originPackage, targetPackage);
|
||||
}
|
||||
if (permission.startsWith("android")
|
||||
|| permission.startsWith("com.android")
|
||||
|| permission.startsWith("com.google.android")) {
|
||||
return permission;
|
||||
}
|
||||
return targetPackage + "_" + permission;
|
||||
});
|
||||
|
||||
property.setAuthorityMapper(value -> {
|
||||
if (value.startsWith(originPackage)) {
|
||||
return value.replaceFirst(originPackage, targetPackage);
|
||||
}
|
||||
return targetPackage + "_" + value;
|
||||
modules.forEach(module -> {
|
||||
property.addMetaData(new ModificationProperty.MetaData("xposedmodule", "true"));
|
||||
property.addMetaData(new ModificationProperty.MetaData("xposeddescription", "NPatch Embed Module"));
|
||||
property.addMetaData(new ModificationProperty.MetaData("xposedminversion", "93"));
|
||||
});
|
||||
|
||||
property.addMetaData(new ModificationProperty.MetaData("npatch", metadata));
|
||||
|
||||
// 注入 MicroG 偽裝簽名與權限
|
||||
if (useMicroG && originalSignature != null && !originalSignature.isEmpty()) {
|
||||
try {
|
||||
byte[] sigBytes = Base64.getDecoder().decode(originalSignature);
|
||||
StringBuilder hex = new StringBuilder();
|
||||
for (byte b : sigBytes) {
|
||||
hex.append(String.format("%02x", b));
|
||||
}
|
||||
property.addMetaData(new ModificationProperty.MetaData("fake-signature", hex.toString()));
|
||||
property.addUsesPermission("android.permission.FAKE_PACKAGE_SIGNATURE");
|
||||
logger.d("Added fake-signature metadata for MicroG compatibility");
|
||||
} catch (Exception e) {
|
||||
logger.e("Failed to add fake-signature: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: replace query_all with queries -> manager
|
||||
if (useManager)
|
||||
property.addUsesPermission("android.permission.QUERY_ALL_PACKAGES");
|
||||
|
||||
// 處理注入 Provider 的邏輯
|
||||
if (isInjectProvider){
|
||||
HashMap<String,String> providerMap = new HashMap<>();
|
||||
providerMap.put("name","bin.mt.file.content.MTDataFilesProvider");
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ public class Constants {
|
|||
final static public String PATCH_FILE_SUFFIX = "-npatched.apk";
|
||||
final static public String PROXY_APP_COMPONENT_FACTORY = "org.lsposed.npatch.metaloader.LSPAppComponentFactoryStub";
|
||||
final static public String MANAGER_PACKAGE_NAME = "org.lsposed.npatch";
|
||||
final static public String REAL_GMS_PACKAGE_NAME = "com.google.android.gms";
|
||||
final static public int MIN_ROLLING_VERSION_CODE = 400;
|
||||
|
||||
final static public int SIGBYPASS_LV_DISABLE = 0;
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ public class PatchConfig {
|
|||
public final LSPConfig lspConfig;
|
||||
public final String managerPackageName;
|
||||
public final String newPackage;
|
||||
public final boolean useMicroG;
|
||||
|
||||
public PatchConfig(
|
||||
boolean useManager,
|
||||
|
|
@ -23,7 +24,8 @@ public class PatchConfig {
|
|||
String appComponentFactory,
|
||||
boolean injectProvider,
|
||||
boolean outputLog,
|
||||
String newPackage
|
||||
String newPackage,
|
||||
boolean useMicroG
|
||||
) {
|
||||
this.useManager = useManager;
|
||||
this.debuggable = debuggable;
|
||||
|
|
@ -36,5 +38,6 @@ public class PatchConfig {
|
|||
this.managerPackageName = Constants.MANAGER_PACKAGE_NAME;
|
||||
this.newPackage = newPackage;
|
||||
this.outputLog = outputLog;
|
||||
this.useMicroG = useMicroG;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue