* Add option to export mappings as Tiny v2 file * Comply with JADX's import order conventions * Only use Java 8 features * Only use Java 8 features (2) * Export comments to mappings file * Method args test (doesn't work) * Make method arg mapping exports work now * Use `getTopParentClass()` instead of `getParentClass()` See https://github.com/skylot/jadx/pull/1505#issuecomment-1145064865 * Remove unneeded method load call * Small code cleanup; initial (broken) support for method vars * Fixes regarding inner classes * Add option to export mappings as Enigma directory * Add option to export mappings as Enigma file/directory Temporarily move to my mapping-io fork until this PR gets merged: https://github.com/FabricMC/mapping-io/pull/19 * Fix method vars' lv-indices * Use correct offset value for method var mappings * Also supply lvt-index for method var mappings * Clarify why we're using local mapping-io fork; comment out Fabric Maven for now * Remove unnecessary `public` modifier * Make an `if` condition less complicated * Move mapping export code into jadx-gui (for now) * Make mapping export async; make export menu only clickable when everything is loaded * Fix export mappings menu field declaration position
This commit is contained in:
@@ -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/'
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -180,12 +180,12 @@ public final class ClassInfo implements Comparable<ClassInfo> {
|
||||
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() {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
@@ -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<VarNode> collectMethodArgs(MethodNode methodNode) {
|
||||
ICodeInfo codeInfo = methodNode.getTopParentClass().getCode();
|
||||
int mthDefPos = methodNode.getDefPosition();
|
||||
int lineEndPos = CodeUtils.getLineEndForPos(codeInfo.getCodeStr(), mthDefPos);
|
||||
List<VarNode> 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<SimpleEntry<VarNode, Integer>> collectMethodVars(MethodNode methodNode) {
|
||||
ICodeInfo codeInfo = methodNode.getTopParentClass().getCode();
|
||||
int mthDefPos = methodNode.getDefPosition();
|
||||
int mthLineEndPos = CodeUtils.getLineEndForPos(codeInfo.getCodeStr(), mthDefPos);
|
||||
|
||||
List<SimpleEntry<VarNode, Integer>> 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, Integer>(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<String> mappedClasses = new HashSet<>();
|
||||
// Map < DeclClass + ShortId >
|
||||
Set<String> mappedFields = new HashSet<>();
|
||||
Set<String> mappedMethods = new HashSet<>();
|
||||
Set<String> methodsWithMappedElements = new HashSet<>();
|
||||
// Map < DeclClass + MethodShortId + CodeRef, NewName >
|
||||
Map<String, String> mappedMethodArgsAndVars = new HashMap<>();
|
||||
// Map < DeclClass + *ShortId + *CodeRef, Comment >
|
||||
Map<String, String> 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<VarNode> 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<SimpleEntry<VarNode, Integer>> vars = collectMethodVars(mth);
|
||||
for (SimpleEntry<VarNode, Integer> 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);
|
||||
}
|
||||
}
|
||||
@@ -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<Path> 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();
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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=正在取消
|
||||
|
||||
|
||||
@@ -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=正在取消
|
||||
|
||||
|
||||
Reference in New Issue
Block a user