refactor class ApkDecoder (#3106)

This commit is contained in:
sv99 2023-06-28 13:13:22 +03:00 committed by GitHub
parent 40d427e5bd
commit 85a710f77a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 129 additions and 141 deletions

View File

@ -19,24 +19,17 @@ package brut.androlib;
import brut.androlib.exceptions.AndrolibException; import brut.androlib.exceptions.AndrolibException;
import brut.androlib.exceptions.InFileNotFoundException; import brut.androlib.exceptions.InFileNotFoundException;
import brut.androlib.exceptions.OutDirExistsException; import brut.androlib.exceptions.OutDirExistsException;
import brut.androlib.exceptions.UndefinedResObjectException;
import brut.androlib.meta.MetaInfo; import brut.androlib.meta.MetaInfo;
import brut.androlib.meta.PackageInfo;
import brut.androlib.meta.UsesFramework;
import brut.androlib.meta.VersionInfo;
import brut.androlib.res.AndrolibResources; import brut.androlib.res.AndrolibResources;
import brut.androlib.res.data.ResPackage;
import brut.androlib.res.data.ResTable; import brut.androlib.res.data.ResTable;
import brut.androlib.res.data.ResUnknownFiles; import brut.androlib.res.data.ResUnknownFiles;
import brut.androlib.src.SmaliDecoder; import brut.androlib.src.SmaliDecoder;
import brut.directory.Directory; import brut.directory.Directory;
import brut.directory.ExtFile; import brut.directory.ExtFile;
import brut.androlib.res.xml.ResXmlPatcher;
import brut.common.BrutException; import brut.common.BrutException;
import brut.directory.DirectoryException; import brut.directory.DirectoryException;
import brut.util.OS; import brut.util.OS;
import com.android.tools.smali.dexlib2.iface.DexFile; import com.android.tools.smali.dexlib2.iface.DexFile;
import com.google.common.base.Strings;
import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.FilenameUtils;
import java.io.File; import java.io.File;
@ -111,7 +104,7 @@ public class ApkDecoder {
if (hasResources()) { if (hasResources()) {
switch (config.decodeResources) { switch (config.decodeResources) {
case Config.DECODE_RESOURCES_NONE: case Config.DECODE_RESOURCES_NONE:
decodeResourcesRaw(mApkFile, outDir); copyResourcesRaw(mApkFile, outDir);
if (config.forceDecodeManifest == Config.FORCE_DECODE_MANIFEST_FULL) { if (config.forceDecodeManifest == Config.FORCE_DECODE_MANIFEST_FULL) {
// done after raw decoding of resources because copyToDir overwrites dest files // done after raw decoding of resources because copyToDir overwrites dest files
if (hasManifest()) { if (hasManifest()) {
@ -135,7 +128,7 @@ public class ApkDecoder {
decodeManifestFull(mApkFile, outDir, getResTable()); decodeManifestFull(mApkFile, outDir, getResTable());
} }
else { else {
decodeManifestRaw(mApkFile, outDir); copyManifestRaw(mApkFile, outDir);
} }
} }
} }
@ -143,7 +136,7 @@ public class ApkDecoder {
if (hasSources()) { if (hasSources()) {
switch (config.decodeSources) { switch (config.decodeSources) {
case Config.DECODE_SOURCES_NONE: case Config.DECODE_SOURCES_NONE:
decodeSourcesRaw(mApkFile, outDir, "classes.dex"); copySourcesRaw(mApkFile, outDir, "classes.dex");
break; break;
case Config.DECODE_SOURCES_SMALI: case Config.DECODE_SOURCES_SMALI:
case Config.DECODE_SOURCES_SMALI_ONLY_MAIN_CLASSES: case Config.DECODE_SOURCES_SMALI_ONLY_MAIN_CLASSES:
@ -160,7 +153,7 @@ public class ApkDecoder {
if (! file.equalsIgnoreCase("classes.dex")) { if (! file.equalsIgnoreCase("classes.dex")) {
switch(config.decodeSources) { switch(config.decodeSources) {
case Config.DECODE_SOURCES_NONE: case Config.DECODE_SOURCES_NONE:
decodeSourcesRaw(mApkFile, outDir, file); copySourcesRaw(mApkFile, outDir, file);
break; break;
case Config.DECODE_SOURCES_SMALI: case Config.DECODE_SOURCES_SMALI:
decodeSourcesSmali(mApkFile, outDir, file); decodeSourcesSmali(mApkFile, outDir, file);
@ -169,7 +162,7 @@ public class ApkDecoder {
if (file.startsWith("classes") && file.endsWith(".dex")) { if (file.startsWith("classes") && file.endsWith(".dex")) {
decodeSourcesSmali(mApkFile, outDir, file); decodeSourcesSmali(mApkFile, outDir, file);
} else { } else {
decodeSourcesRaw(mApkFile, outDir, file); copySourcesRaw(mApkFile, outDir, file);
} }
break; break;
} }
@ -178,11 +171,11 @@ public class ApkDecoder {
} }
} }
decodeRawFiles(mApkFile, outDir); copyRawFiles(mApkFile, outDir);
decodeUnknownFiles(mApkFile, outDir); copyUnknownFiles(mApkFile, outDir);
mUncompressedFiles = new ArrayList<>(); mUncompressedFiles = new ArrayList<>();
recordUncompressedFiles(mApkFile, mUncompressedFiles); recordUncompressedFiles(mApkFile, mUncompressedFiles);
writeOriginalFiles(mApkFile, outDir); copyOriginalFiles(mApkFile, outDir);
writeMetaFile(outDir); writeMetaFile(outDir);
} finally { } finally {
try { try {
@ -250,25 +243,25 @@ public class ApkDecoder {
mAndRes.close(); mAndRes.close();
} }
public void writeMetaFile(File outDir) private void writeMetaFile(File outDir) throws AndrolibException {
throws AndrolibException {
MetaInfo meta = new MetaInfo(); MetaInfo meta = new MetaInfo();
meta.version = ApktoolProperties.getVersion(); meta.version = ApktoolProperties.getVersion();
meta.apkFileName = mApkFile.getName(); meta.apkFileName = mApkFile.getName();
if (mResTable != null) { if (mResTable != null) {
meta.isFrameworkApk = mResTable.isFrameworkApk(); mResTable.initMetaInfo(meta, outDir);
putUsesFramework(meta); if (config.frameworkTag != null) {
putSdkInfo(outDir, meta); meta.usesFramework.tag = config.frameworkTag;
putPackageInfo(meta); }
putVersionInfo(outDir, meta);
putSharedLibraryInfo(meta);
putSparseResourcesInfo(meta);
} else { } else {
putMinSdkInfo(meta); if (mMinSdkVersion > 0) {
meta.sdkInfo = getMinSdkInfo();
}
}
meta.unknownFiles = mResUnknownFiles.getUnknownFiles();
if (mUncompressedFiles != null && !mUncompressedFiles.isEmpty()) {
meta.doNotCompress = mUncompressedFiles;
} }
putUnknownInfo(meta);
putFileCompressionInfo(meta);
try { try {
meta.save(new File(outDir, "apktool.yml")); meta.save(new File(outDir, "apktool.yml"));
@ -277,111 +270,13 @@ public class ApkDecoder {
} }
} }
private void putUsesFramework(MetaInfo meta) { private Map<String, String> getMinSdkInfo() {
Set<ResPackage> pkgs = mResTable.listFramePackages(); Map<String, String> sdkInfo = new LinkedHashMap<>();
if (pkgs.isEmpty()) { sdkInfo.put("minSdkVersion", Integer.toString(mMinSdkVersion));
return; return sdkInfo;
}
Integer[] ids = new Integer[pkgs.size()];
int i = 0;
for (ResPackage pkg : pkgs) {
ids[i++] = pkg.getId();
}
Arrays.sort(ids);
meta.usesFramework = new UsesFramework();
meta.usesFramework.ids = Arrays.asList(ids);
if (config.frameworkTag != null) {
meta.usesFramework.tag = config.frameworkTag;
}
} }
private void putSdkInfo(File outDir, MetaInfo meta) { private void copySourcesRaw(ExtFile apkFile, File outDir, String filename)
Map<String, String> info = mResTable.getSdkInfo();
if (info.size() > 0) {
String refValue;
if (info.get("minSdkVersion") != null) {
refValue = ResXmlPatcher.pullValueFromIntegers(outDir, info.get("minSdkVersion"));
if (refValue != null) {
info.put("minSdkVersion", refValue);
}
}
if (info.get("targetSdkVersion") != null) {
refValue = ResXmlPatcher.pullValueFromIntegers(outDir, info.get("targetSdkVersion"));
if (refValue != null) {
info.put("targetSdkVersion", refValue);
}
}
if (info.get("maxSdkVersion") != null) {
refValue = ResXmlPatcher.pullValueFromIntegers(outDir, info.get("maxSdkVersion"));
if (refValue != null) {
info.put("maxSdkVersion", refValue);
}
}
meta.sdkInfo = info;
}
}
private void putMinSdkInfo(MetaInfo meta) {
if (mMinSdkVersion > 0) {
Map<String, String> sdkInfo = new LinkedHashMap<>();
sdkInfo.put("minSdkVersion", Integer.toString(mMinSdkVersion));
meta.sdkInfo = sdkInfo;
}
}
private void putPackageInfo(MetaInfo meta) throws AndrolibException {
String renamed = mResTable.getPackageRenamed();
String original = mResTable.getPackageOriginal();
int id = mResTable.getPackageId();
try {
id = mResTable.getPackage(renamed).getId();
} catch (UndefinedResObjectException ignored) {}
if (Strings.isNullOrEmpty(original)) {
return;
}
meta.packageInfo = new PackageInfo();
// only put rename-manifest-package into apktool.yml, if the change will be required
if (!renamed.equalsIgnoreCase(original)) {
meta.packageInfo.renameManifestPackage = renamed;
}
meta.packageInfo.forcedPackageId = String.valueOf(id);
}
private void putVersionInfo(File outDir, MetaInfo meta) {
VersionInfo info = mResTable.getVersionInfo();
String refValue = ResXmlPatcher.pullValueFromStrings(outDir, info.versionName);
if (refValue != null) {
info.versionName = refValue;
}
meta.versionInfo = info;
}
private void putSharedLibraryInfo(MetaInfo meta) {
meta.sharedLibrary = mResTable.getSharedLibrary();
}
private void putSparseResourcesInfo(MetaInfo meta) {
meta.sparseResources = mResTable.getSparseResources();
}
private void putUnknownInfo(MetaInfo meta) {
meta.unknownFiles = mResUnknownFiles.getUnknownFiles();
}
private void putFileCompressionInfo(MetaInfo meta) {
if (mUncompressedFiles != null && !mUncompressedFiles.isEmpty()) {
meta.doNotCompress = mUncompressedFiles;
}
}
public void decodeSourcesRaw(ExtFile apkFile, File outDir, String filename)
throws AndrolibException { throws AndrolibException {
try { try {
LOGGER.info("Copying raw " + filename + " file..."); LOGGER.info("Copying raw " + filename + " file...");
@ -391,7 +286,7 @@ public class ApkDecoder {
} }
} }
public void decodeSourcesSmali(File apkFile, File outDir, String filename) private void decodeSourcesSmali(File apkFile, File outDir, String filename)
throws AndrolibException { throws AndrolibException {
try { try {
File smaliDir; File smaliDir;
@ -415,7 +310,7 @@ public class ApkDecoder {
} }
} }
public void decodeManifestRaw(ExtFile apkFile, File outDir) private void copyManifestRaw(ExtFile apkFile, File outDir)
throws AndrolibException { throws AndrolibException {
try { try {
LOGGER.info("Copying raw manifest..."); LOGGER.info("Copying raw manifest...");
@ -425,12 +320,12 @@ public class ApkDecoder {
} }
} }
public void decodeManifestFull(ExtFile apkFile, File outDir, ResTable resTable) private void decodeManifestFull(ExtFile apkFile, File outDir, ResTable resTable)
throws AndrolibException { throws AndrolibException {
mAndRes.decodeManifest(resTable, apkFile, outDir); mAndRes.decodeManifest(resTable, apkFile, outDir);
} }
public void decodeResourcesRaw(ExtFile apkFile, File outDir) private void copyResourcesRaw(ExtFile apkFile, File outDir)
throws AndrolibException { throws AndrolibException {
try { try {
LOGGER.info("Copying raw resources..."); LOGGER.info("Copying raw resources...");
@ -440,17 +335,17 @@ public class ApkDecoder {
} }
} }
public void decodeResourcesFull(ExtFile apkFile, File outDir, ResTable resTable) private void decodeResourcesFull(ExtFile apkFile, File outDir, ResTable resTable)
throws AndrolibException { throws AndrolibException {
mAndRes.decode(resTable, apkFile, outDir); mAndRes.decode(resTable, apkFile, outDir);
} }
public void decodeManifestWithResources(ExtFile apkFile, File outDir, ResTable resTable) private void decodeManifestWithResources(ExtFile apkFile, File outDir, ResTable resTable)
throws AndrolibException { throws AndrolibException {
mAndRes.decodeManifestWithResources(resTable, apkFile, outDir); mAndRes.decodeManifestWithResources(resTable, apkFile, outDir);
} }
public void decodeRawFiles(ExtFile apkFile, File outDir) private void copyRawFiles(ExtFile apkFile, File outDir)
throws AndrolibException { throws AndrolibException {
LOGGER.info("Copying assets and libs..."); LOGGER.info("Copying assets and libs...");
try { try {
@ -484,7 +379,7 @@ public class ApkDecoder {
return false; return false;
} }
public void decodeUnknownFiles(ExtFile apkFile, File outDir) private void copyUnknownFiles(ExtFile apkFile, File outDir)
throws AndrolibException { throws AndrolibException {
LOGGER.info("Copying unknown files..."); LOGGER.info("Copying unknown files...");
File unknownOut = new File(outDir, UNK_DIRNAME); File unknownOut = new File(outDir, UNK_DIRNAME);
@ -508,7 +403,7 @@ public class ApkDecoder {
} }
} }
public void writeOriginalFiles(ExtFile apkFile, File outDir) private void copyOriginalFiles(ExtFile apkFile, File outDir)
throws AndrolibException { throws AndrolibException {
LOGGER.info("Copying original files..."); LOGGER.info("Copying original files...");
File originalDir = new File(outDir, "original"); File originalDir = new File(outDir, "original");
@ -531,7 +426,7 @@ public class ApkDecoder {
if (in.containsDir("META-INF/services")) { if (in.containsDir("META-INF/services")) {
// If the original APK contains the folder META-INF/services folder // If the original APK contains the folder META-INF/services folder
// that is used for service locators (like coroutines on android), // that is used for service locators (like coroutines on android),
// copy it to the destination folder so it does not get dropped. // copy it to the destination folder, so it does not get dropped.
LOGGER.info("Copying META-INF/services directory"); LOGGER.info("Copying META-INF/services directory");
in.copyToDir(outDir, "META-INF/services"); in.copyToDir(outDir, "META-INF/services");
} }
@ -541,7 +436,7 @@ public class ApkDecoder {
} }
} }
public void recordUncompressedFiles(ExtFile apkFile, Collection<String> uncompressedFilesOrExts) throws AndrolibException { private void recordUncompressedFiles(ExtFile apkFile, Collection<String> uncompressedFilesOrExts) throws AndrolibException {
try { try {
Directory unk = apkFile.getDirectory(); Directory unk = apkFile.getDirectory();
Set<String> files = unk.getFiles(true); Set<String> files = unk.getFiles(true);

View File

@ -18,9 +18,16 @@ package brut.androlib.res.data;
import brut.androlib.exceptions.AndrolibException; import brut.androlib.exceptions.AndrolibException;
import brut.androlib.exceptions.UndefinedResObjectException; import brut.androlib.exceptions.UndefinedResObjectException;
import brut.androlib.meta.MetaInfo;
import brut.androlib.meta.PackageInfo;
import brut.androlib.meta.UsesFramework;
import brut.androlib.meta.VersionInfo; import brut.androlib.meta.VersionInfo;
import brut.androlib.res.AndrolibResources; import brut.androlib.res.AndrolibResources;
import brut.androlib.res.data.value.ResValue; import brut.androlib.res.data.value.ResValue;
import brut.androlib.res.xml.ResXmlPatcher;
import com.google.common.base.Strings;
import java.io.File;
import java.util.*; import java.util.*;
public class ResTable { public class ResTable {
@ -220,4 +227,90 @@ public class ResTable {
} }
return false; return false;
} }
public void initMetaInfo(MetaInfo meta, File outDir) throws AndrolibException {
meta.isFrameworkApk = isFrameworkApk();
if (!listFramePackages().isEmpty()) {
meta.usesFramework = getUsesFramework();
}
if (!getSdkInfo().isEmpty()) {
initSdkInfo(outDir);
meta.sdkInfo = getSdkInfo();
}
meta.packageInfo = getPackageInfo();
meta.versionInfo = getVersionInfoWithName(outDir);
meta.sharedLibrary = getSharedLibrary();
meta.sparseResources = getSparseResources();
}
private UsesFramework getUsesFramework() {
Set<ResPackage> pkgs = listFramePackages();
Integer[] ids = new Integer[pkgs.size()];
int i = 0;
for (ResPackage pkg : pkgs) {
ids[i++] = pkg.getId();
}
Arrays.sort(ids);
UsesFramework info = new UsesFramework();
info.ids = Arrays.asList(ids);
return info;
}
private void initSdkInfo(File outDir) {
Map<String, String> info = mSdkInfo;
String refValue;
if (info.get("minSdkVersion") != null) {
refValue = ResXmlPatcher.pullValueFromIntegers(outDir, info.get("minSdkVersion"));
if (refValue != null) {
info.put("minSdkVersion", refValue);
}
}
if (info.get("targetSdkVersion") != null) {
refValue = ResXmlPatcher.pullValueFromIntegers(outDir, info.get("targetSdkVersion"));
if (refValue != null) {
info.put("targetSdkVersion", refValue);
}
}
if (info.get("maxSdkVersion") != null) {
refValue = ResXmlPatcher.pullValueFromIntegers(outDir, info.get("maxSdkVersion"));
if (refValue != null) {
info.put("maxSdkVersion", refValue);
}
}
}
private PackageInfo getPackageInfo() throws AndrolibException {
String renamed = getPackageRenamed();
String original = getPackageOriginal();
int id = getPackageId();
try {
id = getPackage(renamed).getId();
} catch (UndefinedResObjectException ignored) {}
if (Strings.isNullOrEmpty(original)) {
return null;
}
PackageInfo info = new PackageInfo();
// only put rename-manifest-package into apktool.yml, if the change will be required
if (!renamed.equalsIgnoreCase(original)) {
info.renameManifestPackage = renamed;
}
info.forcedPackageId = String.valueOf(id);
return info;
}
private VersionInfo getVersionInfoWithName(File outDir) {
VersionInfo info = getVersionInfo();
String refValue = ResXmlPatcher.pullValueFromStrings(outDir, info.versionName);
if (refValue != null) {
info.versionName = refValue;
}
return info;
}
} }