feat(res): use file headers to detect extension for obfuscated resources (PR #2495)
* feat(res): add feature to use headers for detect resource extensions if resource obfuscated * fix(res): read first 4kb data, for detect headers & use utf8 charset for decode bytes to string
This commit is contained in:
@@ -227,6 +227,12 @@ public class JadxCLIArgs {
|
||||
)
|
||||
protected UseKotlinMethodsForVarNames useKotlinMethodsForVarNames = UseKotlinMethodsForVarNames.APPLY;
|
||||
|
||||
@Parameter(
|
||||
names = { "--use-headers-for-detect-resource-extensions" },
|
||||
description = "Use headers for detect resource extensions if resource obfuscated"
|
||||
)
|
||||
protected boolean useHeadersForDetectResourceExtensions = false;
|
||||
|
||||
@Parameter(
|
||||
names = { "--rename-flags" },
|
||||
description = "fix options (comma-separated list of):"
|
||||
@@ -354,6 +360,7 @@ public class JadxCLIArgs {
|
||||
args.setDeobfuscationMaxLength(deobfuscationMaxLength);
|
||||
args.setDeobfuscationWhitelist(Arrays.asList(deobfuscationWhitelistStr.split(" ")));
|
||||
args.setUseSourceNameAsClassNameAlias(getUseSourceNameAsClassNameAlias());
|
||||
args.setUseHeadersForDetectResourceExtensions(useHeadersForDetectResourceExtensions);
|
||||
args.setSourceNameRepeatLimit(sourceNameRepeatLimit);
|
||||
args.setUseKotlinMethodsForVarNames(useKotlinMethodsForVarNames);
|
||||
args.setResourceNameSource(resourceNameSource);
|
||||
@@ -586,6 +593,10 @@ public class JadxCLIArgs {
|
||||
return fsCaseSensitive;
|
||||
}
|
||||
|
||||
public boolean isUseHeadersForDetectResourceExtensions() {
|
||||
return useHeadersForDetectResourceExtensions;
|
||||
}
|
||||
|
||||
public CommentsLevel getCommentsLevel() {
|
||||
return commentsLevel;
|
||||
}
|
||||
|
||||
@@ -91,6 +91,7 @@ public class JadxArgs implements Closeable {
|
||||
|
||||
private boolean skipResources = false;
|
||||
private boolean skipSources = false;
|
||||
private boolean useHeadersForDetectResourceExtensions;
|
||||
|
||||
/**
|
||||
* Predicate that allows to filter the classes to be process based on their full name
|
||||
@@ -817,6 +818,14 @@ public class JadxArgs implements Closeable {
|
||||
this.loadJadxClsSetFile = loadJadxClsSetFile;
|
||||
}
|
||||
|
||||
public void setUseHeadersForDetectResourceExtensions(boolean useHeadersForDetectResourceExtensions) {
|
||||
this.useHeadersForDetectResourceExtensions = useHeadersForDetectResourceExtensions;
|
||||
}
|
||||
|
||||
public boolean isUseHeadersForDetectResourceExtensions() {
|
||||
return useHeadersForDetectResourceExtensions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash of all options that can change result code
|
||||
*/
|
||||
@@ -825,7 +834,7 @@ public class JadxArgs implements Closeable {
|
||||
+ inlineAnonymousClasses + inlineMethods + moveInnerClasses + allowInlineKotlinLambda
|
||||
+ deobfuscationOn + deobfuscationMinLength + deobfuscationMaxLength + deobfuscationWhitelist
|
||||
+ useSourceNameAsClassNameAlias + sourceNameRepeatLimit
|
||||
+ resourceNameSource
|
||||
+ resourceNameSource + useHeadersForDetectResourceExtensions
|
||||
+ useKotlinMethodsForVarNames
|
||||
+ insertDebugLines + extractFinally
|
||||
+ debugInfo + escapeUnicode + replaceConsts + restoreSwitchOverString
|
||||
@@ -888,6 +897,7 @@ public class JadxArgs implements Closeable {
|
||||
+ ", pluginOptions=" + pluginOptions
|
||||
+ ", cfgOutput=" + cfgOutput
|
||||
+ ", rawCFGOutput=" + rawCFGOutput
|
||||
+ ", useHeadersForDetectResourceExtensions=" + useHeadersForDetectResourceExtensions
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,9 @@ import java.io.File;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.core.deobf.FileTypeDetector;
|
||||
import jadx.core.utils.StringUtils;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
import jadx.core.xmlgen.ResContainer;
|
||||
import jadx.core.xmlgen.entry.ResourceEntry;
|
||||
import jadx.zip.IZipEntry;
|
||||
@@ -11,7 +14,7 @@ import jadx.zip.IZipEntry;
|
||||
public class ResourceFile {
|
||||
private final JadxDecompiler decompiler;
|
||||
private final String name;
|
||||
private final ResourceType type;
|
||||
private ResourceType type;
|
||||
|
||||
private @Nullable IZipEntry zipEntry;
|
||||
private String deobfName;
|
||||
@@ -53,22 +56,63 @@ public class ResourceFile {
|
||||
return ResourcesLoader.loadContent(decompiler, this);
|
||||
}
|
||||
|
||||
public boolean setAlias(ResourceEntry ri) {
|
||||
public boolean setAlias(ResourceEntry entry, boolean useHeders) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("res/").append(ri.getTypeName()).append(ri.getConfig());
|
||||
sb.append("/").append(ri.getKeyName());
|
||||
int lastDot = name.lastIndexOf('.');
|
||||
if (lastDot != -1) {
|
||||
sb.append(name.substring(lastDot));
|
||||
sb.append("res/").append(entry.getTypeName()).append(entry.getConfig());
|
||||
sb.append("/").append(entry.getKeyName());
|
||||
|
||||
if (useHeders) {
|
||||
try {
|
||||
int maxBytesToReadLimit = 4096;
|
||||
byte[] bytes = ResourcesLoader.decodeStream(this, (size, is) -> {
|
||||
int bytesToRead;
|
||||
if (size > 0) {
|
||||
bytesToRead = (int) Math.min(size, maxBytesToReadLimit);
|
||||
} else if (size == 0) {
|
||||
bytesToRead = 0;
|
||||
} else {
|
||||
bytesToRead = maxBytesToReadLimit;
|
||||
}
|
||||
if (bytesToRead == 0) {
|
||||
return new byte[0];
|
||||
}
|
||||
return is.readNBytes(bytesToRead);
|
||||
});
|
||||
|
||||
String fileExtension = FileTypeDetector.detectFileExtension(bytes);
|
||||
if (!StringUtils.isEmpty(fileExtension)) {
|
||||
sb.append(fileExtension);
|
||||
} else {
|
||||
sb.append(getExtFromName(name));
|
||||
}
|
||||
} catch (JadxException ignored) {
|
||||
}
|
||||
} else {
|
||||
sb.append(getExtFromName(name));
|
||||
}
|
||||
String alias = sb.toString();
|
||||
if (!alias.equals(name)) {
|
||||
setDeobfName(alias);
|
||||
type = ResourceType.getFileType(alias);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private String getExtFromName(String name) {
|
||||
// the image .9.png extension always saved, when resource shrinking by aapt2
|
||||
if (name.contains(".9.png")) {
|
||||
return ".9.png";
|
||||
}
|
||||
|
||||
int lastDot = name.lastIndexOf('.');
|
||||
if (lastDot != -1) {
|
||||
return name.substring(lastDot);
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
public @Nullable IZipEntry getZipEntry() {
|
||||
return zipEntry;
|
||||
}
|
||||
|
||||
@@ -165,7 +165,7 @@ public final class ResourcesLoader implements IResourcesLoader {
|
||||
}
|
||||
|
||||
private static ResContainer decodeImage(ResourceFile rf, InputStream inputStream) {
|
||||
String name = rf.getOriginalName();
|
||||
String name = rf.getDeobfName();
|
||||
if (name.endsWith(".9.png")) {
|
||||
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
|
||||
Res9patchStreamDecoder decoder = new Res9patchStreamDecoder();
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
package jadx.core.deobf;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
|
||||
import jadx.core.utils.FileSignature;
|
||||
import jadx.core.utils.StringUtils;
|
||||
|
||||
public class FileTypeDetector {
|
||||
private static final Pattern DOCTYPE_PATTERN = Pattern.compile("\\s*<!doctype *(\\w+)[ >]", Pattern.CASE_INSENSITIVE);
|
||||
private static final List<FileSignature> FILE_SIGNATURES = new ArrayList<>();
|
||||
|
||||
static {
|
||||
register("png", "89 50 4E 47");
|
||||
register("jpg", "FF D8 FF");
|
||||
register("gif", "47 49 46 38");
|
||||
register("webp", "52 49 46 46 ?? ?? ?? ?? 57 45 42 50 56 50 38");
|
||||
register("bmp", "42 4D");
|
||||
register("bmp", "42 41");
|
||||
register("bmp", "43 49");
|
||||
register("bmp", "43 50");
|
||||
register("bmp", "49 43");
|
||||
register("bmp", "50 54");
|
||||
register("mp4", "00 00 00 ?? 66 74 79 70 69 73 6F 36");
|
||||
register("mp4", "00 00 00 ?? 66 74 79 70 6D 70 34 32");
|
||||
register("m4a", "00 00 00 ?? 66 74 79 70 4D 34 41 20");
|
||||
register("mp3", "49 44 33");
|
||||
register("ogg", "4F 67 67 53");
|
||||
register("wav", "52 49 46 46 ?? ?? ?? ?? 57 41 56 45");
|
||||
register("ttf", "00 01 00 00");
|
||||
register("ttc", "74 74 63 66");
|
||||
register("otf", "4F 54 54 4F");
|
||||
register("xml", "03 00 08 00");
|
||||
}
|
||||
|
||||
public static void register(String fileType, String signature) {
|
||||
FILE_SIGNATURES.add(new FileSignature(fileType, signature));
|
||||
}
|
||||
|
||||
private static String detectByHeaders(byte[] data) {
|
||||
for (FileSignature sig : FILE_SIGNATURES) {
|
||||
if (FileSignature.matches(sig, data)) {
|
||||
if (sig.getFileType().equals("png") && isNinePatch(data)) {
|
||||
return ".9.png";
|
||||
}
|
||||
return "." + sig.getFileType();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String detectFileExtension(byte[] data) {
|
||||
// detect ext by headers
|
||||
String extByHeaders = detectByHeaders(data);
|
||||
if (!StringUtils.isEmpty(extByHeaders)) {
|
||||
return extByHeaders;
|
||||
}
|
||||
|
||||
// detect ext by readable text
|
||||
String text = new String(data, StandardCharsets.UTF_8);
|
||||
if (text.startsWith("-----BEGIN CERTIFICATE-----")) {
|
||||
return ".cer";
|
||||
}
|
||||
if (text.startsWith("-----BEGIN PRIVATE KEY-----")) {
|
||||
return ".key";
|
||||
}
|
||||
if (text.contains("<html>")) {
|
||||
return ".html";
|
||||
}
|
||||
Matcher m = DOCTYPE_PATTERN.matcher(text);
|
||||
if (m.lookingAt()) {
|
||||
return "." + m.group(1).toLowerCase();
|
||||
}
|
||||
|
||||
try {
|
||||
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||
factory.setNamespaceAware(true);
|
||||
factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
|
||||
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
|
||||
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
|
||||
|
||||
DocumentBuilder builder = factory.newDocumentBuilder();
|
||||
Document doc = builder.parse(new java.io.ByteArrayInputStream(data));
|
||||
String rootTag = doc.getDocumentElement().getNodeName();
|
||||
|
||||
if ("svg".equalsIgnoreCase(rootTag)) {
|
||||
return ".svg";
|
||||
}
|
||||
if ("plist".equalsIgnoreCase(rootTag)) {
|
||||
return ".plist";
|
||||
}
|
||||
if ("kml".equalsIgnoreCase(rootTag)) {
|
||||
return ".kml";
|
||||
}
|
||||
return ".xml";
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static int readInt(byte[] data, int offset) {
|
||||
return (data[offset] & 0xFF) << 24
|
||||
| (data[offset + 1] & 0xFF) << 16
|
||||
| (data[offset + 2] & 0xFF) << 8
|
||||
| (data[offset + 3] & 0xFF);
|
||||
}
|
||||
|
||||
private static boolean isNinePatch(byte[] data) {
|
||||
int offset = 8;
|
||||
while (offset + 8 < data.length) {
|
||||
int chunkLength = readInt(data, offset);
|
||||
int chunkType = readInt(data, offset + 4);
|
||||
if (chunkType == 0x6e705463) { // 'npTc'
|
||||
return true;
|
||||
}
|
||||
offset += 8 + chunkLength + 4; // chunk + data + CRC
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -271,6 +271,7 @@ public class RootNode {
|
||||
if (args.isSkipResources()) {
|
||||
return;
|
||||
}
|
||||
boolean useHeaders = args.isUseHeadersForDetectResourceExtensions();
|
||||
long start = System.currentTimeMillis();
|
||||
int renamedCount = 0;
|
||||
ResourceStorage resStorage = parser.getResStorage();
|
||||
@@ -285,7 +286,7 @@ public class RootNode {
|
||||
for (ResourceFile resource : resources) {
|
||||
ResourceEntry resEntry = entryNames.get(resource.getOriginalName());
|
||||
if (resEntry != null) {
|
||||
if (resource.setAlias(resEntry)) {
|
||||
if (resource.setAlias(resEntry, useHeaders)) {
|
||||
renamedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package jadx.core.utils;
|
||||
|
||||
public class FileSignature {
|
||||
private final byte[] signatureBytes;
|
||||
private final String fileType;
|
||||
|
||||
public FileSignature(String fileType, String signatureHex) {
|
||||
this.fileType = fileType;
|
||||
String[] parts = signatureHex.split(" ");
|
||||
this.signatureBytes = new byte[parts.length];
|
||||
for (int i = 0; i < parts.length; i++) {
|
||||
if (parts[i].length() != 2) {
|
||||
throw new RuntimeException(signatureHex);
|
||||
}
|
||||
if (!parts[i].equals("??")) {
|
||||
this.signatureBytes[i] = (byte) Integer.parseInt(parts[i], 16);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean matches(FileSignature sig, byte[] data) {
|
||||
if (data.length < sig.signatureBytes.length) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < sig.signatureBytes.length; i++) {
|
||||
byte b = sig.signatureBytes[i];
|
||||
if (b != data[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public String getFileType() {
|
||||
return fileType;
|
||||
}
|
||||
}
|
||||
@@ -446,6 +446,10 @@ public class JadxSettings extends JadxCLIArgs {
|
||||
this.resourceNameSource = source;
|
||||
}
|
||||
|
||||
public void setUseHeadersForDetectResourceExtension(boolean enable) {
|
||||
this.useHeadersForDetectResourceExtensions = enable;
|
||||
}
|
||||
|
||||
public void updateRenameFlag(JadxArgs.RenameEnum flag, boolean enabled) {
|
||||
if (enabled) {
|
||||
renameFlags.add(flag);
|
||||
|
||||
@@ -234,6 +234,13 @@ public class JadxSettingsWindow extends JDialog {
|
||||
needReload();
|
||||
});
|
||||
|
||||
JCheckBox useHeaders = new JCheckBox();
|
||||
useHeaders.setSelected(settings.isUseHeadersForDetectResourceExtensions());
|
||||
useHeaders.addItemListener(e -> {
|
||||
settings.setUseHeadersForDetectResourceExtension(e.getStateChange() == ItemEvent.SELECTED);
|
||||
needReload();
|
||||
});
|
||||
|
||||
JComboBox<GeneratedRenamesMappingFileMode> generatedRenamesMappingFileModeCB =
|
||||
new JComboBox<>(GeneratedRenamesMappingFileMode.values());
|
||||
generatedRenamesMappingFileModeCB.setSelectedItem(settings.getGeneratedRenamesMappingFileMode());
|
||||
@@ -265,6 +272,7 @@ public class JadxSettingsWindow extends JDialog {
|
||||
deobfGroup.addRow(NLS.str("preferences.deobfuscation_min_len"), minLenSpinner);
|
||||
deobfGroup.addRow(NLS.str("preferences.deobfuscation_max_len"), maxLenSpinner);
|
||||
deobfGroup.addRow(NLS.str("preferences.deobfuscation_res_name_source"), resNamesSource);
|
||||
deobfGroup.addRow(NLS.str("preferences.deobfuscation_res_use_headers"), useHeaders);
|
||||
deobfGroup.addRow(NLS.str("preferences.generated_renames_mapping_file_mode"), generatedRenamesMappingFileModeCB);
|
||||
deobfGroup.addRow(NLS.str("preferences.deobfuscation_whitelist"),
|
||||
NLS.str("preferences.deobfuscation_whitelist.tooltip"), editWhitelistedEntities);
|
||||
|
||||
@@ -277,6 +277,7 @@ preferences.generated_renames_mapping_file_mode=Umgang mit Map-Dateien
|
||||
preferences.deobfuscation_min_len=Minimale Namenlänge
|
||||
preferences.deobfuscation_max_len=Maximale Namenlänge
|
||||
preferences.deobfuscation_res_name_source=Bessere Ressourcennamenquelle
|
||||
#preferences.deobfuscation_res_use_headers=Use headers for detect resource extensions
|
||||
preferences.deobfuscation_whitelist=Pakete und Klassen von Deobfuskierung ausschließen
|
||||
preferences.deobfuscation_whitelist.editDialog=Weiße Liste für Deobfuskierung
|
||||
preferences.deobfuscation_whitelist.tooltip=Liste der durch ':' getrennten Pakete (Suffix '.*') und Klassenamen, die nicht deobfuskiert werden sollen
|
||||
|
||||
@@ -277,6 +277,7 @@ preferences.generated_renames_mapping_file_mode=Map file handle mode
|
||||
preferences.deobfuscation_min_len=Minimum name length
|
||||
preferences.deobfuscation_max_len=Maximum name length
|
||||
preferences.deobfuscation_res_name_source=Better resources name source
|
||||
preferences.deobfuscation_res_use_headers=Use headers for detect resource extensions
|
||||
preferences.deobfuscation_whitelist=Exclude packages and classes from deobfuscation
|
||||
preferences.deobfuscation_whitelist.editDialog=Whitelist for deobfuscation
|
||||
preferences.deobfuscation_whitelist.tooltip=List of ':' separated packages (suffix '.*') and class names that will not be deobfuscated
|
||||
|
||||
@@ -277,6 +277,7 @@ preferences.deobfuscation_on=Activar desobfuscación
|
||||
preferences.deobfuscation_min_len=Longitud mínima del nombre
|
||||
preferences.deobfuscation_max_len=Longitud máxima del nombre
|
||||
#preferences.deobfuscation_res_name_source=Better resources name source
|
||||
#preferences.deobfuscation_res_use_headers=Use headers for detect resource extensions
|
||||
#preferences.deobfuscation_whitelist=Exclude packages and classes from deobfuscation
|
||||
#preferences.deobfuscation_whitelist.editDialog=Whitelist for deobfuscation
|
||||
#preferences.deobfuscation_whitelist.tooltip=List of ':' separated packages (suffix '.*') and class names that will not be deobfuscated
|
||||
|
||||
@@ -277,6 +277,7 @@ preferences.generated_renames_mapping_file_mode=Mode penanganan file pemetaan
|
||||
preferences.deobfuscation_min_len=Panjang nama minimum
|
||||
preferences.deobfuscation_max_len=Panjang nama maksimum
|
||||
preferences.deobfuscation_res_name_source=Sumber nama sumber daya yang lebih baik
|
||||
#preferences.deobfuscation_res_use_headers=Use headers for detect resource extensions
|
||||
#preferences.deobfuscation_whitelist=Exclude packages and classes from deobfuscation
|
||||
#preferences.deobfuscation_whitelist.editDialog=Whitelist for deobfuscation
|
||||
#preferences.deobfuscation_whitelist.tooltip=List of ':' separated packages (suffix '.*') and class names that will not be deobfuscated
|
||||
|
||||
@@ -277,6 +277,7 @@ preferences.generated_renames_mapping_file_mode=맵 파일 처리 모드
|
||||
preferences.deobfuscation_min_len=최소 이름 길이
|
||||
preferences.deobfuscation_max_len=최대 이름 길이
|
||||
preferences.deobfuscation_res_name_source=더 나은 리소스 이름 소스
|
||||
#preferences.deobfuscation_res_use_headers=Use headers for detect resource extensions
|
||||
#preferences.deobfuscation_whitelist=Exclude packages and classes from deobfuscation
|
||||
#preferences.deobfuscation_whitelist.editDialog=Whitelist for deobfuscation
|
||||
#preferences.deobfuscation_whitelist.tooltip=List of ':' separated packages (suffix '.*') and class names that will not be deobfuscated
|
||||
|
||||
@@ -277,6 +277,7 @@ preferences.deobfuscation_on=Ativar desofuscação
|
||||
preferences.deobfuscation_min_len=Tamanho mínimo do nome
|
||||
preferences.deobfuscation_max_len=Tamanho máximo do nome
|
||||
preferences.deobfuscation_res_name_source=Melhora nome da fonte dos recursos
|
||||
#preferences.deobfuscation_res_use_headers=Use headers for detect resource extensions
|
||||
#preferences.deobfuscation_whitelist=Exclude packages and classes from deobfuscation
|
||||
#preferences.deobfuscation_whitelist.editDialog=Whitelist for deobfuscation
|
||||
#preferences.deobfuscation_whitelist.tooltip=List of ':' separated packages (suffix '.*') and class names that will not be deobfuscated
|
||||
|
||||
@@ -277,6 +277,7 @@ preferences.generated_renames_mapping_file_mode=Режим обработки м
|
||||
preferences.deobfuscation_min_len=Минимальная длина имени
|
||||
preferences.deobfuscation_max_len=Максимальная длина имени
|
||||
preferences.deobfuscation_res_name_source=Расшифровка имен ресурсов
|
||||
#preferences.deobfuscation_res_use_headers=Use headers for detect resource extensions
|
||||
preferences.deobfuscation_whitelist=Исключить пакеты и классы из деобфускации
|
||||
preferences.deobfuscation_whitelist.editDialog=Белый список деобфускации
|
||||
preferences.deobfuscation_whitelist.tooltip=Разделяйте пакеты через ':' (суффикс '.*') и имя класса которое не будет деобфусцировано
|
||||
|
||||
@@ -277,6 +277,7 @@ preferences.generated_renames_mapping_file_mode=映射文件句柄模式
|
||||
preferences.deobfuscation_min_len=最小命名长度
|
||||
preferences.deobfuscation_max_len=最大命名长度
|
||||
preferences.deobfuscation_res_name_source=更好的资源名称来源
|
||||
#preferences.deobfuscation_res_use_headers=Use headers for detect resource extensions
|
||||
preferences.deobfuscation_whitelist=从反混淆中排除包和类
|
||||
preferences.deobfuscation_whitelist.editDialog=反混淆白名单
|
||||
preferences.deobfuscation_whitelist.tooltip=以‘:’分隔的包(后缀‘.*’)和类名列表,它们不会被反混淆。
|
||||
|
||||
@@ -277,6 +277,7 @@ preferences.generated_renames_mapping_file_mode=Map 檔案處理模式
|
||||
preferences.deobfuscation_min_len=最小名稱長度
|
||||
preferences.deobfuscation_max_len=最大名稱長度
|
||||
preferences.deobfuscation_res_name_source=較佳的資源名稱來源
|
||||
#preferences.deobfuscation_res_use_headers=Use headers for detect resource extensions
|
||||
preferences.deobfuscation_whitelist=去模糊化時排除套件和類別
|
||||
preferences.deobfuscation_whitelist.editDialog=去模糊化白名單
|
||||
preferences.deobfuscation_whitelist.tooltip=將不會被去模糊化的套件(後綴 '.*')和類別名稱清單,以 ':' 分隔
|
||||
|
||||
Reference in New Issue
Block a user