diff --git a/build.gradle b/build.gradle index 322a2d129..10495e892 100644 --- a/build.gradle +++ b/build.gradle @@ -49,6 +49,11 @@ allprojects { mavenLocal() mavenCentral() google() + // Commented out for now since we're using a local mapping-io fork atm. + // maven { + // name 'FabricMC' + // url 'https://maven.fabricmc.net/' + // } } } diff --git a/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java b/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java index be8839a9d..069d00e8e 100644 --- a/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java +++ b/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java @@ -180,12 +180,12 @@ public final class ClassInfo implements Comparable { return makeFullClsName(pkg, name, parentClass, false, true); } - private String makeAliasFullName() { + public String makeAliasFullName() { return makeFullClsName(getAliasPkg(), getAliasShortName(), parentClass, true, false); } - private String makeAliasRawFullName() { - return makeFullClsName(pkg, name, parentClass, true, true); + public String makeAliasRawFullName() { + return makeFullClsName(getAliasPkg(), getAliasShortName(), parentClass, true, true); } public String getAliasFullPath() { diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java index 430abaeba..899d64521 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java @@ -461,6 +461,10 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails, return regsCount; } + public int getArgsStartReg() { + return argsStartReg; + } + public SSAVar makeNewSVar(@NotNull RegisterArg assignArg) { int regNum = assignArg.getRegNum(); return makeNewSVar(regNum, getNextSVarVersion(regNum), assignArg); diff --git a/jadx-gui/build.gradle b/jadx-gui/build.gradle index 0e3c63d16..5dd054b84 100644 --- a/jadx-gui/build.gradle +++ b/jadx-gui/build.gradle @@ -7,7 +7,6 @@ plugins { dependencies { implementation(project(':jadx-core')) - implementation(project(":jadx-cli")) implementation 'com.beust:jcommander:1.82' implementation 'ch.qos.logback:logback-classic:1.2.11' @@ -30,6 +29,11 @@ dependencies { implementation 'com.android.tools.build:apksig:4.2.1' implementation 'io.github.hqktech:jdwp:1.0' + // TODO: Switch back to upstream once this PR gets merged: + // https://github.com/FabricMC/mapping-io/pull/19 + // implementation 'net.fabricmc:mapping-io:0.3.0' + implementation files('libs/mapping-io-0.4.0-SNAPSHOT.jar') + testImplementation project(":jadx-core").sourceSets.test.output } diff --git a/jadx-gui/libs/mapping-io-0.4.0-SNAPSHOT.jar b/jadx-gui/libs/mapping-io-0.4.0-SNAPSHOT.jar new file mode 100644 index 000000000..c5cf62872 Binary files /dev/null and b/jadx-gui/libs/mapping-io-0.4.0-SNAPSHOT.jar differ diff --git a/jadx-gui/src/main/java/jadx/gui/plugins/mappings/MappingExporter.java b/jadx-gui/src/main/java/jadx/gui/plugins/mappings/MappingExporter.java new file mode 100644 index 000000000..f76b3ddc1 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/plugins/mappings/MappingExporter.java @@ -0,0 +1,285 @@ +package jadx.gui.plugins.mappings; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.AbstractMap.SimpleEntry; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.fabricmc.mappingio.MappedElementKind; +import net.fabricmc.mappingio.MappingWriter; +import net.fabricmc.mappingio.format.MappingFormat; +import net.fabricmc.mappingio.tree.MemoryMappingTree; + +import jadx.api.ICodeInfo; +import jadx.api.data.ICodeComment; +import jadx.api.data.ICodeRename; +import jadx.api.data.IJavaNodeRef.RefType; +import jadx.api.data.impl.JadxCodeData; +import jadx.api.data.impl.JadxCodeRef; +import jadx.api.metadata.ICodeNodeRef; +import jadx.api.metadata.annotations.InsnCodeOffset; +import jadx.api.metadata.annotations.NodeDeclareRef; +import jadx.api.metadata.annotations.VarNode; +import jadx.api.utils.CodeUtils; +import jadx.core.Consts; +import jadx.core.codegen.TypeGen; +import jadx.core.dex.info.ClassInfo; +import jadx.core.dex.info.FieldInfo; +import jadx.core.dex.info.MethodInfo; +import jadx.core.dex.nodes.ClassNode; +import jadx.core.dex.nodes.FieldNode; +import jadx.core.dex.nodes.MethodNode; +import jadx.core.dex.nodes.RootNode; +import jadx.core.utils.files.FileUtils; + +public class MappingExporter { + private static final Logger LOG = LoggerFactory.getLogger(MappingExporter.class); + private final RootNode root; + + public MappingExporter(RootNode rootNode) { + this.root = rootNode; + } + + private List collectMethodArgs(MethodNode methodNode) { + ICodeInfo codeInfo = methodNode.getTopParentClass().getCode(); + int mthDefPos = methodNode.getDefPosition(); + int lineEndPos = CodeUtils.getLineEndForPos(codeInfo.getCodeStr(), mthDefPos); + List args = new ArrayList<>(); + codeInfo.getCodeMetadata().searchDown(mthDefPos, (pos, ann) -> { + if (pos > lineEndPos) { + // Stop at line end + return Boolean.TRUE; + } + if (ann instanceof NodeDeclareRef) { + ICodeNodeRef declRef = ((NodeDeclareRef) ann).getNode(); + if (declRef instanceof VarNode) { + VarNode varNode = (VarNode) declRef; + if (!varNode.getMth().equals(methodNode)) { + // Stop if we've gone too far and have entered a different method + return Boolean.TRUE; + } + args.add(varNode); + } + } + return null; + }); + return args; + } + + private List> collectMethodVars(MethodNode methodNode) { + ICodeInfo codeInfo = methodNode.getTopParentClass().getCode(); + int mthDefPos = methodNode.getDefPosition(); + int mthLineEndPos = CodeUtils.getLineEndForPos(codeInfo.getCodeStr(), mthDefPos); + + List> vars = new ArrayList<>(); + AtomicInteger lastOffset = new AtomicInteger(-1); + codeInfo.getCodeMetadata().searchDown(mthLineEndPos, (pos, ann) -> { + if (ann instanceof InsnCodeOffset) { + lastOffset.set(((InsnCodeOffset) ann).getOffset()); + } + if (ann instanceof NodeDeclareRef) { + ICodeNodeRef declRef = ((NodeDeclareRef) ann).getNode(); + if (declRef instanceof VarNode) { + VarNode varNode = (VarNode) declRef; + if (!varNode.getMth().equals(methodNode)) { + // Stop if we've gone too far and have entered a different method + return Boolean.TRUE; + } + if (lastOffset.get() != -1) { + vars.add(new SimpleEntry(varNode, lastOffset.get())); + } else { + LOG.warn("Local variable not present in bytecode, skipping: " + + methodNode.getMethodInfo().getRawFullId() + "#" + varNode.getName()); + } + lastOffset.set(-1); + } + } + return null; + }); + return vars; + } + + public void exportMappings(Path path, JadxCodeData codeData, MappingFormat mappingFormat) { + MemoryMappingTree mappingTree = new MemoryMappingTree(); + // Map < SrcName > + Set mappedClasses = new HashSet<>(); + // Map < DeclClass + ShortId > + Set mappedFields = new HashSet<>(); + Set mappedMethods = new HashSet<>(); + Set methodsWithMappedElements = new HashSet<>(); + // Map < DeclClass + MethodShortId + CodeRef, NewName > + Map mappedMethodArgsAndVars = new HashMap<>(); + // Map < DeclClass + *ShortId + *CodeRef, Comment > + Map comments = new HashMap<>(); + + // We have to do this so we know for sure which elements are *manually* renamed + for (ICodeRename codeRename : codeData.getRenames()) { + if (codeRename.getNodeRef().getType().equals(RefType.CLASS)) { + mappedClasses.add(codeRename.getNodeRef().getDeclaringClass()); + } else if (codeRename.getNodeRef().getType().equals(RefType.FIELD)) { + mappedFields.add(codeRename.getNodeRef().getDeclaringClass() + codeRename.getNodeRef().getShortId()); + } else if (codeRename.getNodeRef().getType().equals(RefType.METHOD)) { + if (codeRename.getCodeRef() == null) { + mappedMethods.add(codeRename.getNodeRef().getDeclaringClass() + codeRename.getNodeRef().getShortId()); + } else { + methodsWithMappedElements.add(codeRename.getNodeRef().getDeclaringClass() + codeRename.getNodeRef().getShortId()); + mappedMethodArgsAndVars.put(codeRename.getNodeRef().getDeclaringClass() + + codeRename.getNodeRef().getShortId() + + codeRename.getCodeRef(), + codeRename.getNewName()); + } + } + } + for (ICodeComment codeComment : codeData.getComments()) { + comments.put(codeComment.getNodeRef().getDeclaringClass() + + (codeComment.getNodeRef().getShortId() == null ? "" : codeComment.getNodeRef().getShortId()) + + (codeComment.getCodeRef() == null ? "" : codeComment.getCodeRef()), + codeComment.getComment()); + if (codeComment.getCodeRef() != null) { + methodsWithMappedElements.add(codeComment.getNodeRef().getDeclaringClass() + codeComment.getNodeRef().getShortId()); + } + } + + try { + if (mappingFormat.hasSingleFile()) { + if (path.toFile().exists()) { + path.toFile().delete(); + } + path.toFile().createNewFile(); + } else { + FileUtils.makeDirs(path); + } + + mappingTree.visitHeader(); + mappingTree.visitNamespaces("official", Arrays.asList("named")); + mappingTree.visitContent(); + + for (ClassNode cls : root.getClasses()) { + ClassInfo classInfo = cls.getClassInfo(); + String classPath = classInfo.makeRawFullName().replace('.', '/'); + String rawClassName = classInfo.getRawName(); + + if (classInfo.hasAlias() + && !classInfo.getAliasShortName().equals(classInfo.getShortName()) + && mappedClasses.contains(rawClassName)) { + mappingTree.visitClass(classPath); + String alias = classInfo.makeAliasRawFullName().replace('.', '/'); + + if (alias.startsWith(Consts.DEFAULT_PACKAGE_NAME)) { + alias = alias.substring(Consts.DEFAULT_PACKAGE_NAME.length() + 1); + } + mappingTree.visitDstName(MappedElementKind.CLASS, 0, alias); + } + if (comments.containsKey(rawClassName)) { + mappingTree.visitClass(classPath); + mappingTree.visitComment(MappedElementKind.CLASS, comments.get(rawClassName)); + } + + for (FieldNode fld : cls.getFields()) { + FieldInfo fieldInfo = fld.getFieldInfo(); + if (fieldInfo.hasAlias() && mappedFields.contains(rawClassName + fieldInfo.getShortId())) { + visitField(mappingTree, classPath, fieldInfo.getName(), TypeGen.signature(fieldInfo.getType())); + mappingTree.visitDstName(MappedElementKind.FIELD, 0, fieldInfo.getAlias()); + } + if (comments.containsKey(rawClassName + fieldInfo.getShortId())) { + visitField(mappingTree, classPath, fieldInfo.getName(), TypeGen.signature(fieldInfo.getType())); + mappingTree.visitComment(MappedElementKind.FIELD, comments.get(rawClassName + fieldInfo.getShortId())); + } + } + + for (MethodNode mth : cls.getMethods()) { + MethodInfo methodInfo = mth.getMethodInfo(); + String methodName = methodInfo.getName(); + String methodDesc = methodInfo.getShortId().substring(methodName.length()); + if (methodInfo.hasAlias() && mappedMethods.contains(rawClassName + methodInfo.getShortId())) { + visitMethod(mappingTree, classPath, methodName, methodDesc); + mappingTree.visitDstName(MappedElementKind.METHOD, 0, methodInfo.getAlias()); + } + if (comments.containsKey(rawClassName + methodInfo.getShortId())) { + visitMethod(mappingTree, classPath, methodName, methodDesc); + mappingTree.visitComment(MappedElementKind.METHOD, comments.get(rawClassName + methodInfo.getShortId())); + } + + if (!methodsWithMappedElements.contains(rawClassName + methodInfo.getShortId())) { + continue; + } + // Method args + int lvtIndex = mth.getAccessFlags().isStatic() ? 0 : 1; + int lastArgLvIndex = lvtIndex - 1; + List args = collectMethodArgs(mth); + for (VarNode arg : args) { + int lvIndex = arg.getReg() - args.get(0).getReg() + (mth.getAccessFlags().isStatic() ? 0 : 1); + String key = rawClassName + methodInfo.getShortId() + + JadxCodeRef.forVar(arg.getReg(), arg.getSsa()); + if (mappedMethodArgsAndVars.containsKey(key)) { + visitMethodArg(mappingTree, classPath, methodName, methodDesc, args.indexOf(arg), lvIndex); + mappingTree.visitDstName(MappedElementKind.METHOD_ARG, 0, mappedMethodArgsAndVars.get(key)); + mappedMethodArgsAndVars.remove(key); + } + lastArgLvIndex = lvIndex; + lvtIndex++; + // Not checking for comments since method args can't have any + } + // Method vars + List> vars = collectMethodVars(mth); + for (SimpleEntry entry : vars) { + VarNode var = entry.getKey(); + int offset = entry.getValue(); + int lvIndex = lastArgLvIndex + var.getReg() + (mth.getAccessFlags().isStatic() ? 0 : 1); + String key = rawClassName + methodInfo.getShortId() + + JadxCodeRef.forVar(var.getReg(), var.getSsa()); + if (mappedMethodArgsAndVars.containsKey(key)) { + visitMethodVar(mappingTree, classPath, methodName, methodDesc, lvtIndex, lvIndex, offset); + mappingTree.visitDstName(MappedElementKind.METHOD_VAR, 0, mappedMethodArgsAndVars.get(key)); + } + key = rawClassName + methodInfo.getShortId() + JadxCodeRef.forInsn(offset); + if (comments.containsKey(key)) { + visitMethodVar(mappingTree, classPath, methodName, methodDesc, lvtIndex, lvIndex, offset); + mappingTree.visitComment(MappedElementKind.METHOD_VAR, comments.get(key)); + } + lvtIndex++; + } + } + } + + MappingWriter writer = MappingWriter.create(path, mappingFormat); + mappingTree.accept(writer); + mappingTree.visitEnd(); + writer.close(); + } catch (IOException e) { + LOG.error("Failed to save deobfuscation map file '{}'", path.toAbsolutePath(), e); + } + } + + private void visitField(MemoryMappingTree tree, String classPath, String srcName, String srcDesc) { + tree.visitClass(classPath); + tree.visitField(srcName, srcDesc); + } + + private void visitMethod(MemoryMappingTree tree, String classPath, String srcName, String srcDesc) { + tree.visitClass(classPath); + tree.visitMethod(srcName, srcDesc); + } + + private void visitMethodArg(MemoryMappingTree tree, String classPath, String methodSrcName, String methodSrcDesc, int argPosition, + int lvIndex) { + visitMethod(tree, classPath, methodSrcName, methodSrcDesc); + tree.visitMethodArg(argPosition, lvIndex, null); + } + + private void visitMethodVar(MemoryMappingTree tree, String classPath, String methodSrcName, String methodSrcDesc, int lvtIndex, + int lvIndex, int startOpIdx) { + visitMethod(tree, classPath, methodSrcName, methodSrcDesc); + tree.visitMethodVar(lvtIndex, lvIndex, startOpIdx, null); + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java index 7bf189ab6..c95c02764 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java @@ -77,12 +77,14 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ch.qos.logback.classic.Level; +import net.fabricmc.mappingio.format.MappingFormat; import jadx.api.JadxArgs; import jadx.api.JavaNode; import jadx.api.ResourceFile; import jadx.api.plugins.utils.CommonFileUtils; import jadx.core.Jadx; +import jadx.core.dex.nodes.RootNode; import jadx.core.utils.ListUtils; import jadx.core.utils.StringUtils; import jadx.core.utils.files.FileUtils; @@ -93,6 +95,7 @@ import jadx.gui.jobs.DecompileTask; import jadx.gui.jobs.ExportTask; import jadx.gui.jobs.ProcessResult; import jadx.gui.jobs.TaskStatus; +import jadx.gui.plugins.mappings.MappingExporter; import jadx.gui.plugins.quark.QuarkDialog; import jadx.gui.settings.JadxProject; import jadx.gui.settings.JadxSettings; @@ -174,6 +177,7 @@ public class MainWindow extends JFrame { private transient Action newProjectAction; private transient Action saveProjectAction; + private transient JMenu exportMappingsMenu; private JPanel mainPanel; private JSplitPane splitPane; @@ -306,6 +310,7 @@ public class MainWindow extends JFrame { return; } closeAll(); + exportMappingsMenu.setEnabled(false); updateProject(new JadxProject(this)); } @@ -349,6 +354,20 @@ public class MainWindow extends JFrame { update(); } + private void exportMappings(MappingFormat mappingFormat) { + RootNode rootNode = wrapper.getDecompiler().getRoot(); + + Thread exportThread = new Thread(() -> { + new MappingExporter(rootNode).exportMappings( + Paths.get(project.getProjectPath().getParent().toString(), + "mappings" + (mappingFormat.hasSingleFile() ? "." + mappingFormat.fileExt : "")), + project.getCodeData(), mappingFormat); + }); + + backgroundExecutor.execute(NLS.str("progress.export_mappings"), exportThread); + update(); + } + void open(List paths) { open(paths, EMPTY_RUNNABLE); } @@ -408,6 +427,7 @@ public class MainWindow extends JFrame { } private void loadFiles(Runnable onFinish) { + exportMappingsMenu.setEnabled(false); if (project.getFilePaths().isEmpty()) { return; } @@ -421,6 +441,7 @@ public class MainWindow extends JFrame { } checkLoadedStatus(); onOpen(); + exportMappingsMenu.setEnabled(true); onFinish.run(); }); } @@ -808,6 +829,36 @@ public class MainWindow extends JFrame { }; saveProjectAsAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("file.save_project_as")); + Action exportMappingsAsTiny2 = new AbstractAction("Tiny v2 file") { + @Override + public void actionPerformed(ActionEvent e) { + exportMappings(MappingFormat.TINY_2); + } + }; + exportMappingsAsTiny2.putValue(Action.SHORT_DESCRIPTION, "Tiny v2 file"); + + Action exportMappingsAsEnigma = new AbstractAction("Enigma file") { + @Override + public void actionPerformed(ActionEvent e) { + exportMappings(MappingFormat.ENIGMA); + } + }; + exportMappingsAsEnigma.putValue(Action.SHORT_DESCRIPTION, "Enigma file"); + + Action exportMappingsAsEnigmaDir = new AbstractAction("Enigma directory") { + @Override + public void actionPerformed(ActionEvent e) { + exportMappings(MappingFormat.ENIGMA_DIR); + } + }; + exportMappingsAsEnigmaDir.putValue(Action.SHORT_DESCRIPTION, "Enigma directory"); + + exportMappingsMenu = new JMenu(NLS.str("file.export_mappings_as")); + exportMappingsMenu.add(exportMappingsAsTiny2); + exportMappingsMenu.add(exportMappingsAsEnigma); + exportMappingsMenu.add(exportMappingsAsEnigmaDir); + exportMappingsMenu.setEnabled(false); + Action saveAllAction = new AbstractAction(NLS.str("file.save_all"), ICON_SAVE_ALL) { @Override public void actionPerformed(ActionEvent e) { @@ -994,6 +1045,8 @@ public class MainWindow extends JFrame { file.add(saveProjectAction); file.add(saveProjectAsAction); file.addSeparator(); + file.add(exportMappingsMenu); + file.addSeparator(); file.add(saveAllAction); file.add(exportAction); file.addSeparator(); 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 63cdadce4..30db55ebc 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties @@ -26,6 +26,7 @@ file.open_title=Datei öffnen file.new_project=Neues Projekt file.save_project=Projekt speichern file.save_project_as=Projekt speichern als… +#file.export_mappings_as= file.save_all=Alles speichern file.export_gradle=Als Gradle-Projekt speichern file.save_all_msg=Verzeichnis für das Speichern dekompilierter Ressourcen auswählen @@ -36,6 +37,7 @@ tree.resources_title=Ressourcen tree.loading=Laden… progress.load=Laden +#progress.export_mappings= progress.decompile=Dekompilieren #progress.canceling=Canceling 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 cc57cc3df..7e9715ffb 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties @@ -26,6 +26,7 @@ file.open_title=Open file file.new_project=New project file.save_project=Save project file.save_project_as=Save project as... +file.export_mappings_as=Export mappings as... file.save_all=Save all file.export_gradle=Save as gradle project file.save_all_msg=Select directory for save decompiled sources @@ -36,6 +37,7 @@ tree.resources_title=Resources tree.loading=Loading... progress.load=Loading +progress.export_mappings=Exporting mappings progress.decompile=Decompiling progress.canceling=Canceling 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 2a5d7e38f..7c380f8d0 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties @@ -26,6 +26,7 @@ file.open_title=Abrir archivo #file.new_project= #file.save_project= #file.save_project_as= +#file.export_mappings_as= file.save_all=Guardar todo file.export_gradle=Guardar como proyecto Gradle file.save_all_msg=Seleccionar carpeta para guardar fuentes descompiladas @@ -36,6 +37,7 @@ tree.resources_title=Recursos tree.loading=Cargando... progress.load=Cargando +#progress.export_mappings= progress.decompile=Decompiling #progress.canceling=Canceling 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 8f0302822..2e6978832 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties @@ -26,6 +26,7 @@ file.open_title=파일 열기 file.new_project=새 프로젝트 file.save_project=프로젝트 저장 file.save_project_as=다른 이름으로 프로젝트 저장... +#file.export_mappings_as= file.save_all=모두 저장 file.export_gradle=Gradle 프로젝트로 저장 file.save_all_msg=디컴파일된 소스를 저장할 디렉토리 선택 @@ -36,6 +37,7 @@ tree.resources_title=리소스 tree.loading=로딩중... progress.load=로딩중 +#progress.export_mappings= progress.decompile=디컴파일 중 #progress.canceling=Canceling 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 e00d4f096..44c3af12d 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties @@ -26,6 +26,7 @@ file.open_title=打开文件 file.new_project=新建项目 file.save_project=保存项目 file.save_project_as=另存项目为... +#file.export_mappings_as= file.save_all=全部保存 file.export_gradle=另存为 Gradle 项目 file.save_all_msg=请选择保存反编译资源的目录 @@ -36,6 +37,7 @@ tree.resources_title=资源文件 tree.loading=加载中... progress.load=正在加载 +#progress.export_mappings= progress.decompile=反编译中 progress.canceling=正在取消 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 1a318d901..5f641c61f 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties @@ -26,6 +26,7 @@ file.open_title=開啟檔案 file.new_project=新建專案 file.save_project=儲存專案 file.save_project_as=另存專案... +#file.export_mappings_as= file.save_all=全部儲存 file.export_gradle=另存為 gradle 專案 file.save_all_msg=選擇儲存反編譯原始碼的路徑 @@ -36,6 +37,7 @@ tree.resources_title=資源 tree.loading=載入中... progress.load=載入中 +#progress.export_mappings= progress.decompile=正在反編譯 progress.canceling=正在取消