refactor: optimize APK processing and cleanup redundant code
Refactored NPatch.java to use NestedZip, optimized dex file counting, and removed unused permission replacement methods. Improved logging and error reporting during the patch process.
This commit is contained in:
parent
c1480fa121
commit
7cf0f3a229
|
|
@ -9,6 +9,7 @@ import static org.lsposed.npatch.share.Constants.PROXY_APP_COMPONENT_FACTORY;
|
||||||
import com.android.tools.build.apkzlib.sign.SigningExtension;
|
import com.android.tools.build.apkzlib.sign.SigningExtension;
|
||||||
import com.android.tools.build.apkzlib.sign.SigningOptions;
|
import com.android.tools.build.apkzlib.sign.SigningOptions;
|
||||||
import com.android.tools.build.apkzlib.zip.AlignmentRules;
|
import com.android.tools.build.apkzlib.zip.AlignmentRules;
|
||||||
|
import com.android.tools.build.apkzlib.zip.NestedZip;
|
||||||
import com.android.tools.build.apkzlib.zip.StoredEntry;
|
import com.android.tools.build.apkzlib.zip.StoredEntry;
|
||||||
import com.android.tools.build.apkzlib.zip.ZFile;
|
import com.android.tools.build.apkzlib.zip.ZFile;
|
||||||
import com.android.tools.build.apkzlib.zip.ZFileOptions;
|
import com.android.tools.build.apkzlib.zip.ZFileOptions;
|
||||||
|
|
@ -18,11 +19,8 @@ import com.beust.jcommander.ParameterException;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.wind.meditor.core.ManifestEditor;
|
import com.wind.meditor.core.ManifestEditor;
|
||||||
import com.wind.meditor.property.AttributeItem;
|
import com.wind.meditor.property.AttributeItem;
|
||||||
import com.wind.meditor.property.AttributeMapper;
|
|
||||||
import com.wind.meditor.property.ModificationProperty;
|
import com.wind.meditor.property.ModificationProperty;
|
||||||
import com.wind.meditor.property.PermissionMapper;
|
|
||||||
import com.wind.meditor.utils.NodeValue;
|
import com.wind.meditor.utils.NodeValue;
|
||||||
import com.wind.meditor.utils.PermissionType;
|
|
||||||
|
|
||||||
import org.apache.commons.io.FilenameUtils;
|
import org.apache.commons.io.FilenameUtils;
|
||||||
import org.lsposed.npatch.share.Constants;
|
import org.lsposed.npatch.share.Constants;
|
||||||
|
|
@ -47,7 +45,6 @@ import java.util.Arrays;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
@ -86,7 +83,7 @@ public class NPatch {
|
||||||
@Parameter(names = {"-l", "--sigbypasslv"}, description = "Signature bypass level. 0 (disable), 1 (pm), 2 (pm+openat). default 0")
|
@Parameter(names = {"-l", "--sigbypasslv"}, description = "Signature bypass level. 0 (disable), 1 (pm), 2 (pm+openat). default 0")
|
||||||
private int sigbypassLevel = 0;
|
private int sigbypassLevel = 0;
|
||||||
|
|
||||||
@Parameter(names = {"--injectdex"}, description = "Inject directly the loder dex file into the original application package")
|
@Parameter(names = {"--injectdex"}, description = "Inject directly the loader dex file into the original application package")
|
||||||
private boolean injectDex = false;
|
private boolean injectDex = false;
|
||||||
|
|
||||||
@Parameter(names = {"--provider"}, description = "Inject Provider to manager data files")
|
@Parameter(names = {"--provider"}, description = "Inject Provider to manager data files")
|
||||||
|
|
@ -127,7 +124,6 @@ public class NPatch {
|
||||||
));
|
));
|
||||||
|
|
||||||
private final JCommander jCommander;
|
private final JCommander jCommander;
|
||||||
|
|
||||||
private final Logger logger;
|
private final Logger logger;
|
||||||
|
|
||||||
public NPatch(Logger logger, String... args) {
|
public NPatch(Logger logger, String... args) {
|
||||||
|
|
@ -171,6 +167,7 @@ public class NPatch {
|
||||||
String apkFileName = srcApkFile.getName();
|
String apkFileName = srcApkFile.getName();
|
||||||
|
|
||||||
var outputDir = new File(outputPath);
|
var outputDir = new File(outputPath);
|
||||||
|
//noinspection ResultOfMethodCallIgnored
|
||||||
outputDir.mkdirs();
|
outputDir.mkdirs();
|
||||||
|
|
||||||
File outputFile = new File(outputDir, String.format(
|
File outputFile = new File(outputDir, String.format(
|
||||||
|
|
@ -180,7 +177,7 @@ public class NPatch {
|
||||||
).getAbsoluteFile();
|
).getAbsoluteFile();
|
||||||
|
|
||||||
if (outputFile.exists() && !forceOverwrite)
|
if (outputFile.exists() && !forceOverwrite)
|
||||||
throw new PatchError(outputPath + " exists. Use --force to overwrite");
|
throw new PatchError(outputFile.getAbsolutePath() + " exists. Use --force to overwrite");
|
||||||
logger.i("Processing " + srcApkFile + " -> " + outputFile);
|
logger.i("Processing " + srcApkFile + " -> " + outputFile);
|
||||||
|
|
||||||
patch(srcApkFile, outputFile);
|
patch(srcApkFile, outputFile);
|
||||||
|
|
@ -191,14 +188,15 @@ public class NPatch {
|
||||||
if (!srcApkFile.exists())
|
if (!srcApkFile.exists())
|
||||||
throw new PatchError("The source apk file does not exit. Please provide a correct path.");
|
throw new PatchError("The source apk file does not exit. Please provide a correct path.");
|
||||||
|
|
||||||
|
//noinspection ResultOfMethodCallIgnored
|
||||||
outputFile.delete();
|
outputFile.delete();
|
||||||
|
|
||||||
logger.d("apk path: " + srcApkFile);
|
logger.d("apk path: " + srcApkFile);
|
||||||
|
|
||||||
logger.i("Parsing original apk...");
|
logger.i("Parsing original apk...");
|
||||||
|
|
||||||
try (var dstZFile = ZFile.openReadWrite(outputFile, Z_FILE_OPTIONS);
|
try (ZFile dstZFile = ZFile.openReadWrite(outputFile, Z_FILE_OPTIONS);
|
||||||
var srcZFile = ZFile.openReadOnly(srcApkFile)) {
|
NestedZip srcZFile = dstZFile.addNestedZip((ignore) -> ORIGINAL_APK_ASSET_PATH, srcApkFile, false)) {
|
||||||
|
|
||||||
// sign apk
|
// sign apk
|
||||||
try {
|
try {
|
||||||
|
|
@ -259,10 +257,6 @@ public class NPatch {
|
||||||
newPackage = pair.packageName;
|
newPackage = pair.packageName;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.i("permissions: " + pair.permissions);
|
|
||||||
logger.i("use-permissions: " +pair.use_permissions);
|
|
||||||
logger.i("provider.authorities: " + pair.authorities);
|
|
||||||
|
|
||||||
logger.i("permissions size: " + (pair.permissions == null ? 0 : pair.permissions.size()));
|
logger.i("permissions size: " + (pair.permissions == null ? 0 : pair.permissions.size()));
|
||||||
logger.i("use-permissions size: " + (pair.use_permissions == null ? 0 : pair.use_permissions.size()));
|
logger.i("use-permissions size: " + (pair.use_permissions == null ? 0 : pair.use_permissions.size()));
|
||||||
logger.i("authorities size: " + (pair.authorities == null ? 0 : pair.authorities.size()));
|
logger.i("authorities size: " + (pair.authorities == null ? 0 : pair.authorities.size()));
|
||||||
|
|
@ -286,7 +280,7 @@ public class NPatch {
|
||||||
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);
|
||||||
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, pair.permissions, pair.use_permissions, pair.authorities))) {
|
try (var is = new ByteArrayInputStream(modifyManifestFile(manifestEntry.open(), metadata, minSdkVersion, pair.packageName, newPackage))) {
|
||||||
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);
|
||||||
|
|
@ -302,13 +296,14 @@ public class NPatch {
|
||||||
|
|
||||||
logger.i("Adding metaloader dex...");
|
logger.i("Adding metaloader dex...");
|
||||||
try (var is = getClass().getClassLoader().getResourceAsStream(Constants.META_LOADER_DEX_ASSET_PATH)) {
|
try (var is = getClass().getClassLoader().getResourceAsStream(Constants.META_LOADER_DEX_ASSET_PATH)) {
|
||||||
|
if (is == null) throw new PatchError("Meta loader dex not found");
|
||||||
if (!injectDex) {
|
if (!injectDex) {
|
||||||
dstZFile.add("classes.dex", is);
|
dstZFile.add("classes.dex", is);
|
||||||
} else {
|
} else {
|
||||||
var dexCount = srcZFile.entries().stream().filter(entry -> {
|
var dexCount = srcZFile.entries().stream().filter(entry -> {
|
||||||
var name = entry.getCentralDirectoryHeader().getName();
|
var name = entry.getCentralDirectoryHeader().getName();
|
||||||
return name.startsWith("classes") && name.endsWith(".dex");
|
return name.startsWith("classes") && name.endsWith(".dex");
|
||||||
}).collect(Collectors.toList()).size() + 1;
|
}).count() + 1; // Used .count() instead of .collect().size() for efficiency
|
||||||
dstZFile.add("classes" + dexCount + ".dex", is);
|
dstZFile.add("classes" + dexCount + ".dex", is);
|
||||||
}
|
}
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
|
@ -348,7 +343,12 @@ public class NPatch {
|
||||||
for (String arch : ARCHES) {
|
for (String arch : ARCHES) {
|
||||||
String entryName = "assets/npatch/so/" + arch + "/libnpatch.so";
|
String entryName = "assets/npatch/so/" + arch + "/libnpatch.so";
|
||||||
try (var is = getClass().getClassLoader().getResourceAsStream(entryName)) {
|
try (var is = getClass().getClassLoader().getResourceAsStream(entryName)) {
|
||||||
dstZFile.add(entryName, is, false); // no compress for so
|
if (is != null) {
|
||||||
|
dstZFile.add(entryName, is, false); // no compress for so
|
||||||
|
logger.d("added " + entryName);
|
||||||
|
} else {
|
||||||
|
logger.e("Native lib not found: " + entryName);
|
||||||
|
}
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
// More exception info
|
// More exception info
|
||||||
throw new PatchError("Error when adding native lib", e);
|
throw new PatchError("Error when adding native lib", e);
|
||||||
|
|
@ -368,7 +368,8 @@ public class NPatch {
|
||||||
if (dstZFile.get(name) != null) continue;
|
if (dstZFile.get(name) != null) continue;
|
||||||
if (!injectDex && name.startsWith("classes") && name.endsWith(".dex")) continue;
|
if (!injectDex && name.startsWith("classes") && name.endsWith(".dex")) continue;
|
||||||
if (name.equals("AndroidManifest.xml")) continue;
|
if (name.equals("AndroidManifest.xml")) continue;
|
||||||
if (name.startsWith("META-INF") && (name.endsWith(".SF") || name.endsWith(".MF") || name.endsWith(".RSA"))) continue;
|
if (name.startsWith("META-INF") && (name.endsWith(".SF") || name.endsWith(".MF") || name.endsWith(".RSA")))
|
||||||
|
continue;
|
||||||
|
|
||||||
try (InputStream is = entry.open()) {
|
try (InputStream is = entry.open()) {
|
||||||
if (name.endsWith(".so") || name.equals("resources.arsc")) {
|
if (name.endsWith(".so") || name.equals("resources.arsc")) {
|
||||||
|
|
@ -382,81 +383,33 @@ public class NPatch {
|
||||||
}
|
}
|
||||||
|
|
||||||
dstZFile.realign();
|
dstZFile.realign();
|
||||||
|
|
||||||
logger.i("Writing apk...");
|
logger.i("Writing apk...");
|
||||||
}
|
}
|
||||||
logger.i("Done. Output APK: " + outputFile.getAbsolutePath());
|
logger.i("Done. Output APK: " + outputFile.getAbsolutePath());
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<String> replacePermissionWithNewPackage(List<String> list, String pkg, String newPackage){
|
|
||||||
List<String> res = new LinkedList<>();
|
|
||||||
if (list != null && !list.isEmpty()){
|
|
||||||
for (String next : list) {
|
|
||||||
if (next != null && !next.isEmpty()) {
|
|
||||||
if (next.startsWith(pkg)){
|
|
||||||
String s = next.replaceAll(pkg, newPackage);
|
|
||||||
res.add(s);
|
|
||||||
}else {
|
|
||||||
res.add(newPackage + "_" + next);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<String> replaceUsesPermissionWithNewPackage(List<String> list, String pkg, String newPackage){
|
|
||||||
List<String> res = new LinkedList<>();
|
|
||||||
if (list != null && !list.isEmpty()){
|
|
||||||
for (String next : list) {
|
|
||||||
if (next != null && !next.isEmpty()) {
|
|
||||||
if (next.startsWith(pkg)){
|
|
||||||
String s = next.replaceAll(pkg, newPackage);
|
|
||||||
res.add(s);
|
|
||||||
}else {
|
|
||||||
res.add(newPackage + "_" + next);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<String> replaceProviderWithNewPackage(List<String> list, String pkg, String newPackage){
|
|
||||||
List<String> res = new LinkedList<>();
|
|
||||||
if (list != null && !list.isEmpty()){
|
|
||||||
for (String next : list) {
|
|
||||||
if (next != null && !next.isEmpty()) {
|
|
||||||
if (next.startsWith(pkg)){
|
|
||||||
String s = next.replaceAll(pkg, newPackage);
|
|
||||||
res.add(s);
|
|
||||||
}else {
|
|
||||||
res.add(newPackage + "_" + next);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void embedModules(ZFile zFile) {
|
private void embedModules(ZFile zFile) {
|
||||||
for (var module : modules) {
|
for (var module : modules) {
|
||||||
File file = new File(module);
|
File file = new File(module);
|
||||||
try (var apk = ZFile.openReadOnly(new File(module));
|
try (var apk = ZFile.openReadOnly(new File(module));
|
||||||
var fileIs = new FileInputStream(file);
|
var fileIs = new FileInputStream(file)) {
|
||||||
var xmlIs = Objects.requireNonNull(apk.get(ANDROID_MANIFEST_XML)).open()
|
|
||||||
) {
|
var manifestEntry = apk.get(ANDROID_MANIFEST_XML);
|
||||||
var manifest = Objects.requireNonNull(ManifestParser.parseManifestFile(xmlIs));
|
if (manifestEntry == null) throw new IOException("Manifest not found in module");
|
||||||
var packageName = manifest.packageName;
|
|
||||||
logger.i(" - " + packageName);
|
try (var xmlIs = manifestEntry.open()) {
|
||||||
zFile.add(EMBEDDED_MODULES_ASSET_PATH + packageName + ".apk", fileIs);
|
var manifest = Objects.requireNonNull(ManifestParser.parseManifestFile(xmlIs));
|
||||||
} catch (NullPointerException | IOException e) {
|
var packageName = manifest.packageName;
|
||||||
|
logger.i(" - " + packageName);
|
||||||
|
zFile.add(EMBEDDED_MODULES_ASSET_PATH + packageName + ".apk", fileIs);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
logger.e(module + " does not exist or is not a valid apk file. error:" + e);
|
logger.e(module + " does not exist or is not a valid apk file. error:" + e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] modifyManifestFile(InputStream is, String metadata, int minSdkVersion, String originPackage, String newPackage, List<String> permissions, List<String> uses_permissions, List<String> authorities) throws IOException {
|
private byte[] modifyManifestFile(InputStream is, String metadata, int minSdkVersion, String originPackage, String newPackage) 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;
|
||||||
|
|
@ -493,12 +446,13 @@ public class NPatch {
|
||||||
// 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");
|
||||||
|
|
||||||
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");
|
||||||
providerMap.put("permission","android.permission.MANAGE_DOCUMENTS");
|
providerMap.put("permission","android.permission.MANAGE_DOCUMENTS");
|
||||||
providerMap.put("exported","true");
|
providerMap.put("exported","true");
|
||||||
providerMap.put("authorities",packageName + ".MTDataFilesProvider");
|
providerMap.put("authorities", targetPackage + ".MTDataFilesProvider");
|
||||||
providerMap.put("grantUriPermissions","true");
|
providerMap.put("grantUriPermissions","true");
|
||||||
|
|
||||||
property.addProvider(providerMap,"android.content.action.DOCUMENTS_PROVIDER");
|
property.addProvider(providerMap,"android.content.action.DOCUMENTS_PROVIDER");
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue