From 86582de5211ccd009ba115ebe12ed4ddcbbf3923 Mon Sep 17 00:00:00 2001 From: Skylot Date: Wed, 19 Jan 2022 17:30:04 +0000 Subject: [PATCH] feat: use kotlin intrinsic methods for variables rename (#1207) --- README.md | 3 +- .../src/main/java/jadx/cli/JadxCLIArgs.java | 30 ++- .../src/main/java/jadx/api/JadxArgs.java | 15 ++ jadx-core/src/main/java/jadx/core/Jadx.java | 4 + .../kotlin/ProcessKotlinInternals.java | 226 ++++++++++++++++++ .../java/jadx/gui/settings/JadxSettings.java | 10 +- .../jadx/gui/settings/JadxSettingsWindow.java | 9 + .../resources/i18n/Messages_de_DE.properties | 1 + .../resources/i18n/Messages_en_US.properties | 1 + .../resources/i18n/Messages_es_ES.properties | 1 + .../resources/i18n/Messages_ko_KR.properties | 1 + .../resources/i18n/Messages_zh_CN.properties | 1 + .../resources/i18n/Messages_zh_TW.properties | 3 +- 13 files changed, 300 insertions(+), 5 deletions(-) create mode 100644 jadx-core/src/main/java/jadx/core/dex/visitors/kotlin/ProcessKotlinInternals.java diff --git a/README.md b/README.md index 0e1aa18fe..70f065d4e 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java b/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java index 7cbf5441a..6b22b6f95 100644 --- a/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java +++ b/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java @@ -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 { + @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(", ")); } } diff --git a/jadx-core/src/main/java/jadx/api/JadxArgs.java b/jadx-core/src/main/java/jadx/api/JadxArgs.java index 9680cffd1..0e6e12e13 100644 --- a/jadx-core/src/main/java/jadx/api/JadxArgs.java +++ b/jadx-core/src/main/java/jadx/api/JadxArgs.java @@ -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 diff --git a/jadx-core/src/main/java/jadx/core/Jadx.java b/jadx-core/src/main/java/jadx/core/Jadx.java index 02f89ab8b..0c42052fb 100644 --- a/jadx-core/src/main/java/jadx/core/Jadx.java +++ b/jadx-core/src/main/java/jadx/core/Jadx.java @@ -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()); diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/kotlin/ProcessKotlinInternals.java b/jadx-core/src/main/java/jadx/core/dex/visitors/kotlin/ProcessKotlinInternals.java new file mode 100644 index 000000000..573185915 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/kotlin/ProcessKotlinInternals.java @@ -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 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 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 collectMethods(ClassNode kotlinCls) { + Set 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; + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java index 5376fdf9a..1b94f20aa 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java @@ -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"); } diff --git a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java index b18784b6c..c51e6a7f7 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java @@ -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 kotlinRenameVars = new JComboBox<>(UseKotlinMethodsForVarNames.values()); + kotlinRenameVars.setSelectedItem(settings.getUseKotlinMethodsForVarNames()); + kotlinRenameVars.addActionListener(e -> { + settings.setUseKotlinMethodsForVarNames((UseKotlinMethodsForVarNames) kotlinRenameVars.getSelectedItem()); + needReload(); + }); + JComboBox 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; } diff --git a/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties b/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties index 2d3d8715a..fe5063007 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties @@ -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 diff --git a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties index 67716a632..96cc05b89 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties @@ -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 diff --git a/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties b/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties index 65f0134e8..db0bbdf61 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties @@ -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 diff --git a/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties b/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties index 1591a4151..95351970f 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties @@ -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=처리 스레드 수 diff --git a/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties b/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties index 6fdb62979..b521ad99b 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties @@ -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=并行线程数 diff --git a/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties b/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties index 3419c46cd..d96fbfd9c 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties @@ -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=排除於索引或反編譯外的套件列表 (以空格分隔) (節省 RAM)
例如 android.support preferences.cfg=產生方法 CFG 圖表 ('dot' 格式)