feat: use kotlin intrinsic methods for variables rename (#1207)
This commit is contained in:
@@ -96,6 +96,7 @@ options:
|
||||
--deobf-rewrite-cfg - force to ignore and overwrite deobfuscation map file
|
||||
--deobf-use-sourcename - use source file name as class name alias
|
||||
--deobf-parse-kotlin-metadata - parse kotlin metadata to class and package names
|
||||
--use-kotlin-methods-for-var-names - use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide, default: apply
|
||||
--rename-flags - fix options (comma-separated list of):
|
||||
'case' - fix case sensitivity issues (according to --fs-case-sensitive option),
|
||||
'valid' - rename java identifiers to make them valid,
|
||||
@@ -107,7 +108,7 @@ options:
|
||||
--raw-cfg - save methods control flow graph (use raw instructions)
|
||||
-f, --fallback - make simple dump (using goto instead of 'if', 'for', etc)
|
||||
--use-dx - use dx/d8 to convert java bytecode
|
||||
--comments-level - set code comments level, values: none, user_only, error, warn, info, debug, default: info
|
||||
--comments-level - set code comments level, values: error, warn, info, debug, user-only, none, default: info
|
||||
--log-level - set log level, values: quiet, progress, error, warn, info, debug, default: progress
|
||||
-v, --verbose - verbose output (set --log-level to DEBUG)
|
||||
-q, --quiet - turn off output (set --log-level to QUIET)
|
||||
|
||||
@@ -14,6 +14,7 @@ import com.beust.jcommander.Parameter;
|
||||
import jadx.api.CommentsLevel;
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.api.JadxArgs.RenameEnum;
|
||||
import jadx.api.JadxArgs.UseKotlinMethodsForVarNames;
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
@@ -101,6 +102,13 @@ public class JadxCLIArgs {
|
||||
@Parameter(names = { "--deobf-parse-kotlin-metadata" }, description = "parse kotlin metadata to class and package names")
|
||||
protected boolean deobfuscationParseKotlinMetadata = false;
|
||||
|
||||
@Parameter(
|
||||
names = { "--use-kotlin-methods-for-var-names" },
|
||||
description = "use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide",
|
||||
converter = UseKotlinMethodsForVarNamesConverter.class
|
||||
)
|
||||
protected UseKotlinMethodsForVarNames useKotlinMethodsForVarNames = UseKotlinMethodsForVarNames.APPLY;
|
||||
|
||||
@Parameter(
|
||||
names = { "--rename-flags" },
|
||||
description = "fix options (comma-separated list of):"
|
||||
@@ -130,7 +138,7 @@ public class JadxCLIArgs {
|
||||
|
||||
@Parameter(
|
||||
names = { "--comments-level" },
|
||||
description = "set code comments level, values: error, warn, info, debug, user_only, none",
|
||||
description = "set code comments level, values: error, warn, info, debug, user-only, none",
|
||||
converter = CommentsLevelConverter.class
|
||||
)
|
||||
protected CommentsLevel commentsLevel = CommentsLevel.INFO;
|
||||
@@ -223,6 +231,7 @@ public class JadxCLIArgs {
|
||||
args.setDeobfuscationMaxLength(deobfuscationMaxLength);
|
||||
args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias);
|
||||
args.setParseKotlinMetadata(deobfuscationParseKotlinMetadata);
|
||||
args.setUseKotlinMethodsForVarNames(useKotlinMethodsForVarNames);
|
||||
args.setEscapeUnicode(escapeUnicode);
|
||||
args.setRespectBytecodeAccModifiers(respectBytecodeAccessModifiers);
|
||||
args.setExportAsGradleProject(exportAsGradleProject);
|
||||
@@ -326,6 +335,10 @@ public class JadxCLIArgs {
|
||||
return deobfuscationParseKotlinMetadata;
|
||||
}
|
||||
|
||||
public UseKotlinMethodsForVarNames getUseKotlinMethodsForVarNames() {
|
||||
return useKotlinMethodsForVarNames;
|
||||
}
|
||||
|
||||
public boolean isEscapeUnicode() {
|
||||
return escapeUnicode;
|
||||
}
|
||||
@@ -412,9 +425,22 @@ public class JadxCLIArgs {
|
||||
}
|
||||
}
|
||||
|
||||
public static class UseKotlinMethodsForVarNamesConverter implements IStringConverter<UseKotlinMethodsForVarNames> {
|
||||
@Override
|
||||
public UseKotlinMethodsForVarNames convert(String value) {
|
||||
try {
|
||||
return UseKotlinMethodsForVarNames.valueOf(value.replace('-', '_').toUpperCase());
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException(
|
||||
'\'' + value + "' is unknown, possible values are: "
|
||||
+ JadxCLIArgs.enumValuesString(CommentsLevel.values()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static String enumValuesString(Enum<?>[] values) {
|
||||
return Stream.of(values)
|
||||
.map(v -> v.name().toLowerCase(Locale.ROOT))
|
||||
.map(v -> v.name().replace('_', '-').toLowerCase(Locale.ROOT))
|
||||
.collect(Collectors.joining(", "));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,6 +87,12 @@ public class JadxArgs {
|
||||
|
||||
private boolean useDxInput = false;
|
||||
|
||||
public enum UseKotlinMethodsForVarNames {
|
||||
DISABLE, APPLY, APPLY_AND_HIDE
|
||||
}
|
||||
|
||||
private UseKotlinMethodsForVarNames useKotlinMethodsForVarNames = UseKotlinMethodsForVarNames.APPLY;
|
||||
|
||||
public JadxArgs() {
|
||||
// use default options
|
||||
}
|
||||
@@ -433,6 +439,14 @@ public class JadxArgs {
|
||||
this.useDxInput = useDxInput;
|
||||
}
|
||||
|
||||
public UseKotlinMethodsForVarNames getUseKotlinMethodsForVarNames() {
|
||||
return useKotlinMethodsForVarNames;
|
||||
}
|
||||
|
||||
public void setUseKotlinMethodsForVarNames(UseKotlinMethodsForVarNames useKotlinMethodsForVarNames) {
|
||||
this.useKotlinMethodsForVarNames = useKotlinMethodsForVarNames;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "JadxArgs{" + "inputFiles=" + inputFiles
|
||||
@@ -452,6 +466,7 @@ public class JadxArgs {
|
||||
+ ", deobfuscationForceSave=" + deobfuscationForceSave
|
||||
+ ", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias
|
||||
+ ", parseKotlinMetadata=" + parseKotlinMetadata
|
||||
+ ", useKotlinMethodsForVarNames=" + useKotlinMethodsForVarNames
|
||||
+ ", deobfuscationMinLength=" + deobfuscationMinLength
|
||||
+ ", deobfuscationMaxLength=" + deobfuscationMaxLength
|
||||
+ ", escapeUnicode=" + escapeUnicode
|
||||
|
||||
@@ -48,6 +48,7 @@ import jadx.core.dex.visitors.blocks.BlockSplitter;
|
||||
import jadx.core.dex.visitors.debuginfo.DebugInfoApplyVisitor;
|
||||
import jadx.core.dex.visitors.debuginfo.DebugInfoAttachVisitor;
|
||||
import jadx.core.dex.visitors.finaly.MarkFinallyVisitor;
|
||||
import jadx.core.dex.visitors.kotlin.ProcessKotlinInternals;
|
||||
import jadx.core.dex.visitors.regions.CheckRegions;
|
||||
import jadx.core.dex.visitors.regions.CleanRegions;
|
||||
import jadx.core.dex.visitors.regions.IfRegionVisitor;
|
||||
@@ -131,6 +132,9 @@ public class Jadx {
|
||||
if (args.isDebugInfo()) {
|
||||
passes.add(new DebugInfoApplyVisitor());
|
||||
}
|
||||
if (args.getUseKotlinMethodsForVarNames() != JadxArgs.UseKotlinMethodsForVarNames.DISABLE) {
|
||||
passes.add(new ProcessKotlinInternals());
|
||||
}
|
||||
passes.add(new CodeRenameVisitor());
|
||||
if (args.isInlineMethods()) {
|
||||
passes.add(new InlineMethods());
|
||||
|
||||
@@ -0,0 +1,226 @@
|
||||
package jadx.core.dex.visitors.kotlin;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.JadxArgs.UseKotlinMethodsForVarNames;
|
||||
import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
||||
import jadx.core.deobf.NameMapper;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.ConstStringNode;
|
||||
import jadx.core.dex.instructions.IndexInsnNode;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.InvokeNode;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.InsnWrapArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.dex.visitors.AbstractVisitor;
|
||||
import jadx.core.dex.visitors.InitCodeVariables;
|
||||
import jadx.core.dex.visitors.JadxVisitor;
|
||||
import jadx.core.dex.visitors.debuginfo.DebugInfoApplyVisitor;
|
||||
import jadx.core.dex.visitors.rename.CodeRenameVisitor;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
|
||||
@JadxVisitor(
|
||||
name = "ProcessKotlinInternals",
|
||||
desc = "Use variable names from Kotlin intrinsic1 methods",
|
||||
runAfter = {
|
||||
InitCodeVariables.class,
|
||||
DebugInfoApplyVisitor.class
|
||||
},
|
||||
runBefore = {
|
||||
CodeRenameVisitor.class
|
||||
}
|
||||
)
|
||||
public class ProcessKotlinInternals extends AbstractVisitor {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ProcessKotlinInternals.class);
|
||||
|
||||
private static final String KOTLIN_INTERNAL_PKG = "kotlin.jvm.internal.";
|
||||
private static final String KOTLIN_INTRINSICS_CLS = KOTLIN_INTERNAL_PKG + "Intrinsics";
|
||||
private static final String KOTLIN_VARNAME_SOURCE_MTH1 = "(Ljava/lang/Object;Ljava/lang/String;)V";
|
||||
private static final String KOTLIN_VARNAME_SOURCE_MTH2 = "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;)V";
|
||||
|
||||
private @Nullable ClassInfo kotlinIntrinsicsCls;
|
||||
private Set<MethodInfo> kotlinVarNameSourceMethods;
|
||||
private boolean hideInsns;
|
||||
|
||||
@Override
|
||||
public void init(RootNode root) throws JadxException {
|
||||
ClassNode kotlinCls = searchKotlinIntrinsicsClass(root);
|
||||
if (kotlinCls != null) {
|
||||
kotlinIntrinsicsCls = kotlinCls.getClassInfo();
|
||||
kotlinVarNameSourceMethods = collectMethods(kotlinCls);
|
||||
LOG.debug("Kotlin Intrinsics class: {}, methods: {}", kotlinCls, kotlinVarNameSourceMethods.size());
|
||||
} else {
|
||||
kotlinIntrinsicsCls = null;
|
||||
LOG.debug("Kotlin Intrinsics class not found");
|
||||
}
|
||||
hideInsns = root.getArgs().getUseKotlinMethodsForVarNames() == UseKotlinMethodsForVarNames.APPLY_AND_HIDE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(ClassNode cls) {
|
||||
if (kotlinIntrinsicsCls == null) {
|
||||
return false;
|
||||
}
|
||||
for (MethodNode mth : cls.getMethods()) {
|
||||
processMth(mth);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void processMth(MethodNode mth) {
|
||||
if (mth.isNoCode()) {
|
||||
return;
|
||||
}
|
||||
for (BlockNode block : mth.getBasicBlocks()) {
|
||||
for (InsnNode insn : block.getInstructions()) {
|
||||
if (insn.getType() == InsnType.INVOKE) {
|
||||
try {
|
||||
processInvoke(mth, insn);
|
||||
} catch (Exception e) {
|
||||
mth.addWarnComment("Failed to extract var names", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processInvoke(MethodNode mth, InsnNode insn) {
|
||||
int argsCount = insn.getArgsCount();
|
||||
if (argsCount < 2) {
|
||||
return;
|
||||
}
|
||||
MethodInfo invokeMth = ((InvokeNode) insn).getCallMth();
|
||||
if (!kotlinVarNameSourceMethods.contains(invokeMth)) {
|
||||
return;
|
||||
}
|
||||
InsnArg firstArg = insn.getArg(0);
|
||||
if (!firstArg.isRegister()) {
|
||||
return;
|
||||
}
|
||||
RegisterArg varArg = (RegisterArg) firstArg;
|
||||
boolean renamed = false;
|
||||
if (argsCount == 2) {
|
||||
String str = getConstString(mth, insn, 1);
|
||||
if (str != null) {
|
||||
renamed = checkAndRename(varArg, str);
|
||||
}
|
||||
} else if (argsCount == 3) {
|
||||
// TODO: use second arg for rename class
|
||||
String str = getConstString(mth, insn, 2);
|
||||
if (str != null) {
|
||||
renamed = checkAndRename(varArg, str);
|
||||
}
|
||||
}
|
||||
if (renamed && hideInsns) {
|
||||
insn.add(AFlag.DONT_GENERATE);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkAndRename(RegisterArg arg, String str) {
|
||||
String name = trimName(str);
|
||||
if (NameMapper.isValidAndPrintable(name)) {
|
||||
arg.getSVar().getCodeVar().setName(name);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String getConstString(MethodNode mth, InsnNode insn, int arg) {
|
||||
InsnArg strArg = insn.getArg(arg);
|
||||
if (!strArg.isInsnWrap()) {
|
||||
return null;
|
||||
}
|
||||
InsnNode constInsn = ((InsnWrapArg) strArg).getWrapInsn();
|
||||
InsnType insnType = constInsn.getType();
|
||||
if (insnType == InsnType.CONST_STR) {
|
||||
return ((ConstStringNode) constInsn).getString();
|
||||
}
|
||||
if (insnType == InsnType.SGET) {
|
||||
// revert const field inline :(
|
||||
FieldInfo fieldInfo = (FieldInfo) ((IndexInsnNode) constInsn).getIndex();
|
||||
FieldNode fieldNode = mth.root().resolveField(fieldInfo);
|
||||
if (fieldNode != null) {
|
||||
String str = (String) fieldNode.get(JadxAttrType.CONSTANT_VALUE).getValue();
|
||||
InsnArg newArg = InsnArg.wrapArg(new ConstStringNode(str));
|
||||
insn.replaceArg(strArg, newArg);
|
||||
return str;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String trimName(String str) {
|
||||
if (str.startsWith("$this$")) {
|
||||
return str.substring(6);
|
||||
}
|
||||
if (str.startsWith("$")) {
|
||||
return str.substring(1);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static ClassNode searchKotlinIntrinsicsClass(RootNode root) {
|
||||
ClassNode kotlinCls = root.resolveClass(KOTLIN_INTRINSICS_CLS);
|
||||
if (kotlinCls != null) {
|
||||
return kotlinCls;
|
||||
}
|
||||
List<ClassNode> candidates = new ArrayList<>();
|
||||
for (ClassNode cls : root.getClasses()) {
|
||||
if (isKotlinIntrinsicsClass(cls)) {
|
||||
candidates.add(cls);
|
||||
}
|
||||
}
|
||||
return Utils.getOne(candidates);
|
||||
}
|
||||
|
||||
private static boolean isKotlinIntrinsicsClass(ClassNode cls) {
|
||||
if (!cls.getClassInfo().getFullName().startsWith(KOTLIN_INTERNAL_PKG)) {
|
||||
return false;
|
||||
}
|
||||
if (cls.getMethods().size() < 5) {
|
||||
return false;
|
||||
}
|
||||
int mthCount = 0;
|
||||
for (MethodNode mth : cls.getMethods()) {
|
||||
if (mth.getAccessFlags().isStatic()
|
||||
&& mth.getMethodInfo().getShortId().endsWith(KOTLIN_VARNAME_SOURCE_MTH1)) {
|
||||
mthCount++;
|
||||
}
|
||||
}
|
||||
return mthCount > 2;
|
||||
}
|
||||
|
||||
private Set<MethodInfo> collectMethods(ClassNode kotlinCls) {
|
||||
Set<MethodInfo> set = new HashSet<>();
|
||||
for (MethodNode mth : kotlinCls.getMethods()) {
|
||||
if (!mth.getAccessFlags().isStatic()) {
|
||||
continue;
|
||||
}
|
||||
String shortId = mth.getMethodInfo().getShortId();
|
||||
if (shortId.endsWith(KOTLIN_VARNAME_SOURCE_MTH1) || shortId.endsWith(KOTLIN_VARNAME_SOURCE_MTH2)) {
|
||||
set.add(mth.getMethodInfo());
|
||||
}
|
||||
}
|
||||
return set;
|
||||
}
|
||||
}
|
||||
@@ -43,7 +43,7 @@ public class JadxSettings extends JadxCLIArgs {
|
||||
|
||||
private static final Path USER_HOME = Paths.get(System.getProperty("user.home"));
|
||||
private static final int RECENT_PROJECTS_COUNT = 15;
|
||||
private static final int CURRENT_SETTINGS_VERSION = 14;
|
||||
private static final int CURRENT_SETTINGS_VERSION = 15;
|
||||
|
||||
private static final Font DEFAULT_FONT = new RSyntaxTextArea().getFont();
|
||||
|
||||
@@ -338,6 +338,10 @@ public class JadxSettings extends JadxCLIArgs {
|
||||
this.deobfuscationParseKotlinMetadata = deobfuscationParseKotlinMetadata;
|
||||
}
|
||||
|
||||
public void setUseKotlinMethodsForVarNames(JadxArgs.UseKotlinMethodsForVarNames useKotlinMethodsForVarNames) {
|
||||
this.useKotlinMethodsForVarNames = useKotlinMethodsForVarNames;
|
||||
}
|
||||
|
||||
public void updateRenameFlag(JadxArgs.RenameEnum flag, boolean enabled) {
|
||||
if (enabled) {
|
||||
renameFlags.add(flag);
|
||||
@@ -651,6 +655,10 @@ public class JadxSettings extends JadxCLIArgs {
|
||||
lafTheme = LafManager.INITIAL_THEME_NAME;
|
||||
fromVersion++;
|
||||
}
|
||||
if (fromVersion == 14) {
|
||||
useKotlinMethodsForVarNames = JadxArgs.UseKotlinMethodsForVarNames.APPLY;
|
||||
fromVersion++;
|
||||
}
|
||||
if (fromVersion != CURRENT_SETTINGS_VERSION) {
|
||||
throw new JadxRuntimeException("Incorrect settings upgrade");
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ import say.swing.JFontChooser;
|
||||
|
||||
import jadx.api.CommentsLevel;
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.api.JadxArgs.UseKotlinMethodsForVarNames;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.ui.codearea.EditorTheme;
|
||||
import jadx.gui.utils.FontUtils;
|
||||
@@ -510,6 +511,13 @@ public class JadxSettingsWindow extends JDialog {
|
||||
needReload();
|
||||
});
|
||||
|
||||
JComboBox<UseKotlinMethodsForVarNames> kotlinRenameVars = new JComboBox<>(UseKotlinMethodsForVarNames.values());
|
||||
kotlinRenameVars.setSelectedItem(settings.getUseKotlinMethodsForVarNames());
|
||||
kotlinRenameVars.addActionListener(e -> {
|
||||
settings.setUseKotlinMethodsForVarNames((UseKotlinMethodsForVarNames) kotlinRenameVars.getSelectedItem());
|
||||
needReload();
|
||||
});
|
||||
|
||||
JComboBox<CommentsLevel> commentsLevel = new JComboBox<>(CommentsLevel.values());
|
||||
commentsLevel.setSelectedItem(settings.getCommentsLevel());
|
||||
commentsLevel.addActionListener(e -> {
|
||||
@@ -533,6 +541,7 @@ public class JadxSettingsWindow extends JDialog {
|
||||
other.addRow(NLS.str("preferences.fallback"), fallback);
|
||||
other.addRow(NLS.str("preferences.useDx"), useDx);
|
||||
other.addRow(NLS.str("preferences.skipResourcesDecode"), resourceDecode);
|
||||
other.addRow(NLS.str("preferences.useKotlinMethodsForVarNames"), kotlinRenameVars);
|
||||
other.addRow(NLS.str("preferences.commentsLevel"), commentsLevel);
|
||||
return other;
|
||||
}
|
||||
|
||||
@@ -134,6 +134,7 @@ preferences.inlineAnonymous=Anonyme Inline-Klassen
|
||||
preferences.inlineMethods=Inline-Methoden
|
||||
preferences.fsCaseSensitive=Dateisystem unterscheidet zwischen Groß/Kleinschreibung
|
||||
preferences.skipResourcesDecode=Keine Ressourcen dekodieren
|
||||
#preferences.useKotlinMethodsForVarNames=Use kotlin methods for variables rename
|
||||
#preferences.commentsLevel=Code comments level
|
||||
preferences.autoSave=Autom. speichern
|
||||
preferences.threads=Verarbeitungs-Thread-Anzahl
|
||||
|
||||
@@ -134,6 +134,7 @@ preferences.inlineAnonymous=Inline anonymous classes
|
||||
preferences.inlineMethods=Inline methods
|
||||
preferences.fsCaseSensitive=File system is case sensitive
|
||||
preferences.skipResourcesDecode=Don't decode resources
|
||||
preferences.useKotlinMethodsForVarNames=Use kotlin methods for variables rename
|
||||
preferences.commentsLevel=Code comments level
|
||||
preferences.autoSave=Auto save
|
||||
preferences.threads=Processing threads count
|
||||
|
||||
@@ -134,6 +134,7 @@ preferences.replaceConsts=Reemplazar constantes
|
||||
#preferences.inlineMethods=Inline methods
|
||||
#preferences.fsCaseSensitive=
|
||||
preferences.skipResourcesDecode=No descodificar recursos
|
||||
#preferences.useKotlinMethodsForVarNames=Use kotlin methods for variables rename
|
||||
#preferences.commentsLevel=Code comments level
|
||||
#preferences.autoSave=
|
||||
preferences.threads=Número de hilos a procesar
|
||||
|
||||
@@ -134,6 +134,7 @@ preferences.inlineAnonymous=인라인 익명 클래스
|
||||
preferences.inlineMethods=인라인 메서드
|
||||
preferences.fsCaseSensitive=파일 시스템 대소문자 구별
|
||||
preferences.skipResourcesDecode=리소스 디코딩 하지 않기
|
||||
#preferences.useKotlinMethodsForVarNames=Use kotlin methods for variables rename
|
||||
preferences.commentsLevel=코드 주석 수준
|
||||
preferences.autoSave=자동 저장
|
||||
preferences.threads=처리 스레드 수
|
||||
|
||||
@@ -134,6 +134,7 @@ preferences.inlineAnonymous=内联匿名类
|
||||
preferences.inlineMethods=内联方法
|
||||
preferences.fsCaseSensitive=文件系统区分大小写
|
||||
preferences.skipResourcesDecode=不反编译资源文件
|
||||
#preferences.useKotlinMethodsForVarNames=Use kotlin methods for variables rename
|
||||
preferences.commentsLevel=代码注释等级
|
||||
preferences.autoSave=自动保存
|
||||
preferences.threads=并行线程数
|
||||
|
||||
@@ -134,11 +134,12 @@ preferences.inlineAnonymous=內嵌匿名類別
|
||||
preferences.inlineMethods=內嵌方式
|
||||
preferences.fsCaseSensitive=檔案系統區分大小寫
|
||||
preferences.skipResourcesDecode=不要為資源解碼
|
||||
#preferences.useKotlinMethodsForVarNames=Use kotlin methods for variables rename
|
||||
preferences.commentsLevel=程式碼註解層級
|
||||
preferences.autoSave=自動儲存
|
||||
preferences.threads=執行緒數
|
||||
preferences.excludedPackages=被排除的套件
|
||||
preferences.excludedPackages.tooltip=排除於索引或反編譯外的套件列表 (以空格分隔) (節省 RAM)
|
||||
preferences.excludedPackages.tooltip=排除於索引或反編譯外的套件列表 (以空格分隔) (節省 RAM)
|
||||
preferences.excludedPackages.button=編輯
|
||||
preferences.excludedPackages.editDialog=<html>排除於索引或反編譯外的套件列表 (以空格分隔) (節省 RAM) <br>例如 <code>android.support</code></html>
|
||||
preferences.cfg=產生方法 CFG 圖表 ('dot' 格式)
|
||||
|
||||
Reference in New Issue
Block a user