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:
NkBe 2026-03-08 21:37:52 +08:00
parent 01d344ed01
commit e640da4f57
No known key found for this signature in database
GPG Key ID: 9FACEE0DB6DF678E
11 changed files with 318 additions and 25 deletions

View File

@ -36,6 +36,7 @@ object Patcher {
} }
if (config.injectProvider) add("--provider") if (config.injectProvider) add("--provider")
if(injectDex) add("--injectdex") if(injectDex) add("--injectdex")
if (config.useMicroG) add("--useMicroG")
if (!MyKeyStore.useDefault) { if (!MyKeyStore.useDefault) {
addAll(arrayOf("-k", MyKeyStore.file.path, Configs.keyStorePassword, Configs.keyStoreAlias, Configs.keyStoreAliasPassword)) addAll(arrayOf("-k", MyKeyStore.file.path, Configs.keyStorePassword, Configs.keyStoreAlias, Configs.keyStoreAliasPassword))
} }

View File

@ -365,6 +365,13 @@ private fun PatchOptionsBody(modifier: Modifier, onAddEmbed: () -> Unit) {
title = stringResource(R.string.patch_inject_mt_provider), title = stringResource(R.string.patch_inject_mt_provider),
desc = stringResource(R.string.patch_inject_mt_provider_desc) 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( SettingsCheckBox(
modifier = Modifier.clickable { viewModel.outputLog = !viewModel.outputLog }, modifier = Modifier.clickable { viewModel.outputLog = !viewModel.outputLog },
checked = viewModel.outputLog, checked = viewModel.outputLog,

View File

@ -47,6 +47,7 @@ class NewPatchViewModel : ViewModel() {
var injectDex by mutableStateOf(false) var injectDex by mutableStateOf(false)
var injectProvider by mutableStateOf(false) var injectProvider by mutableStateOf(false)
var outputLog by mutableStateOf(true) var outputLog by mutableStateOf(true)
var useMicroG by mutableStateOf(false)
var embeddedModules = emptyList<AppInfo>() var embeddedModules = emptyList<AppInfo>()
lateinit var patchApp: AppInfo lateinit var patchApp: AppInfo
@ -99,7 +100,7 @@ class NewPatchViewModel : ViewModel() {
private fun submitPatch() { private fun submitPatch() {
Log.d(TAG, "Submit Patch") Log.d(TAG, "Submit Patch")
if (useManager) embeddedModules = emptyList() 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( patchOptions = Patcher.Options(
newPackageName = newPackageName, newPackageName = newPackageName,
injectDex = injectDex, injectDex = injectDex,

View File

@ -73,7 +73,8 @@
<string name="patch_inject_mt_provider_desc">注入文件提供器以在没有 Root 权限的情况下管理 data 目录的文件 (来自 MT 管理器)</string> <string name="patch_inject_mt_provider_desc">注入文件提供器以在没有 Root 权限的情况下管理 data 目录的文件 (来自 MT 管理器)</string>
<string name="patch_inject_dex">注入加载器 Dex</string> <string name="patch_inject_dex">注入加载器 Dex</string>
<string name="patch_inject_dex_desc">对那些需要孤立服务进程的应用程序,譬如说浏览器的渲染引擎,请勾选此选项以确保他们正常运行</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_output_log_to_media_desc">将模块的 Xposed 日志输出到目标应用的 Media 目录</string>
<string name="patch_start">开始修补</string> <string name="patch_start">开始修补</string>
<string name="patch_return">返回</string> <string name="patch_return">返回</string>

View File

@ -73,6 +73,8 @@
<string name="patch_inject_mt_provider_desc">注入檔案选取器以在沒有 Root 權限的情況下管理 data 目錄的檔案(來自 MT 管理器)</string> <string name="patch_inject_mt_provider_desc">注入檔案选取器以在沒有 Root 權限的情況下管理 data 目錄的檔案(來自 MT 管理器)</string>
<string name="patch_inject_dex">注入載入器 Dex</string> <string name="patch_inject_dex">注入載入器 Dex</string>
<string name="patch_inject_dex_desc">對那些需要孤立服務程序的應用程式,譬如說瀏覽器的渲染引擎,請勾選此選項以確保他們正常執行</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">日誌輸出到 Media 目錄</string>
<string name="patch_output_log_to_media_desc">將模組的 Xposed 日誌輸出到目標應用的 Media 目錄</string> <string name="patch_output_log_to_media_desc">將模組的 Xposed 日誌輸出到目標應用的 Media 目錄</string>
<string name="patch_start">開始打包</string> <string name="patch_start">開始打包</string>

View File

@ -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_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">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_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">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_output_log_to_media_desc">Output the Xposed log to the target application Media directory.</string>
<string name="patch_start">Start Patch</string> <string name="patch_start">Start Patch</string>

View File

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

View File

@ -145,6 +145,11 @@ public class LSPApplication {
switchAllClassLoader(); switchAllClassLoader();
SigBypass.doSigBypass(context, config.sigBypassLevel); 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"); Log.i(TAG, "NPatch bootstrap completed");
} }

View File

@ -89,6 +89,12 @@ public class NPatch {
@Parameter(names = {"--provider"}, description = "Inject Provider to manager data files") @Parameter(names = {"--provider"}, description = "Inject Provider to manager data files")
private boolean isInjectProvider = false; 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") @Parameter(names = {"--outputLog"}, description = "Output Log to Media")
private boolean outputLog = true; private boolean outputLog = true;
@ -287,10 +293,10 @@ public class NPatch {
logger.i("Patching apk..."); logger.i("Patching apk...");
// modify manifest // 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 configBytes = new Gson().toJson(config).getBytes(StandardCharsets.UTF_8);
final var metadata = Base64.getEncoder().encodeToString(configBytes); 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); dstZFile.add(ANDROID_MANIFEST_XML, is);
} catch (Throwable e) { } catch (Throwable e) {
throw new PatchError("Error when modifying manifest", 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(); ModificationProperty property = new ModificationProperty();
String targetPackage = (newPackage != null && !newPackage.isEmpty()) ? newPackage : originPackage; String targetPackage = (newPackage != null && !newPackage.isEmpty()) ? newPackage : originPackage;
if (overrideVersionCode) if (minSdkVersion > 0)
property.addManifestAttribute(new AttributeItem(NodeValue.Manifest.VERSION_CODE, 1)); property.addUsesSdkAttribute(new AttributeItem(NodeValue.UsesSDK.MIN_SDK_VERSION, minSdkVersion));
if (minSdkVersion < 28) else
property.addUsesSdkAttribute(new AttributeItem(NodeValue.UsesSDK.MIN_SDK_VERSION, 27)); property.addUsesSdkAttribute(new AttributeItem(NodeValue.UsesSDK.MIN_SDK_VERSION, 27));
property.addApplicationAttribute(new AttributeItem(NodeValue.Application.DEBUGGABLE, debuggableFlag)); property.addApplicationAttribute(new AttributeItem(NodeValue.Application.DEBUGGABLE, debuggableFlag));
property.addApplicationAttribute(new AttributeItem("appComponentFactory", PROXY_APP_COMPONENT_FACTORY)); property.addApplicationAttribute(new AttributeItem("appComponentFactory", PROXY_APP_COMPONENT_FACTORY));
property.addApplicationAttribute(new AttributeItem("isSplitRequired", false));
if (!targetPackage.equals(originPackage)) { if (!targetPackage.equals(originPackage)) {
property.addManifestAttribute(new AttributeItem(NodeValue.Manifest.PACKAGE, targetPackage).setNamespace(null)); 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 -> { modules.forEach(module -> {
if (value.startsWith(originPackage)) { property.addMetaData(new ModificationProperty.MetaData("xposedmodule", "true"));
return value.replaceFirst(originPackage, targetPackage); property.addMetaData(new ModificationProperty.MetaData("xposeddescription", "NPatch Embed Module"));
} property.addMetaData(new ModificationProperty.MetaData("xposedminversion", "93"));
return targetPackage + "_" + value;
}); });
property.addMetaData(new ModificationProperty.MetaData("npatch", metadata)); 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 // TODO: replace query_all with queries -> manager
if (useManager) if (useManager)
property.addUsesPermission("android.permission.QUERY_ALL_PACKAGES"); property.addUsesPermission("android.permission.QUERY_ALL_PACKAGES");
// 處理注入 Provider 的邏輯
if (isInjectProvider){ if (isInjectProvider){
HashMap<String,String> providerMap = new HashMap<>(); HashMap<String,String> providerMap = new HashMap<>();
providerMap.put("name","bin.mt.file.content.MTDataFilesProvider"); providerMap.put("name","bin.mt.file.content.MTDataFilesProvider");

View File

@ -12,6 +12,7 @@ public class Constants {
final static public String PATCH_FILE_SUFFIX = "-npatched.apk"; 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 PROXY_APP_COMPONENT_FACTORY = "org.lsposed.npatch.metaloader.LSPAppComponentFactoryStub";
final static public String MANAGER_PACKAGE_NAME = "org.lsposed.npatch"; 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 MIN_ROLLING_VERSION_CODE = 400;
final static public int SIGBYPASS_LV_DISABLE = 0; final static public int SIGBYPASS_LV_DISABLE = 0;

View File

@ -13,6 +13,7 @@ public class PatchConfig {
public final LSPConfig lspConfig; public final LSPConfig lspConfig;
public final String managerPackageName; public final String managerPackageName;
public final String newPackage; public final String newPackage;
public final boolean useMicroG;
public PatchConfig( public PatchConfig(
boolean useManager, boolean useManager,
@ -23,7 +24,8 @@ public class PatchConfig {
String appComponentFactory, String appComponentFactory,
boolean injectProvider, boolean injectProvider,
boolean outputLog, boolean outputLog,
String newPackage String newPackage,
boolean useMicroG
) { ) {
this.useManager = useManager; this.useManager = useManager;
this.debuggable = debuggable; this.debuggable = debuggable;
@ -36,5 +38,6 @@ public class PatchConfig {
this.managerPackageName = Constants.MANAGER_PACKAGE_NAME; this.managerPackageName = Constants.MANAGER_PACKAGE_NAME;
this.newPackage = newPackage; this.newPackage = newPackage;
this.outputLog = outputLog; this.outputLog = outputLog;
this.useMicroG = useMicroG;
} }
} }