Commit 74a72a5c authored by Ahmed Ashour's avatar Ahmed Ashour Committed by skylot

feat: add options to configure "renaming" (#570) (PR #582)

parent a1bfdc63
package jadx.cli;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.stream.Collectors;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import com.beust.jcommander.Parameter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.beust.jcommander.IStringConverter;
import com.beust.jcommander.Parameter;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import jadx.api.JadxArgs;
import jadx.api.JadxArgs.RENAME;
import jadx.api.JadxDecompiler;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.files.FileUtils;
......@@ -83,6 +90,10 @@ public class JadxCLIArgs {
@Parameter(names = {"-f", "--fallback"}, description = "make simple dump (using goto instead of 'if', 'for', etc)")
protected boolean fallbackMode = false;
@Parameter(names = {"--rename-flags"}, description = "what to rename, comma-separated, 'case' for system case sensitivity, 'valid' for java identifiers, 'printable' characters, 'none' or 'all'",
converter = RenameConverter.class)
protected Set<RENAME> renameFlags = EnumSet.allOf(RENAME.class);
@Parameter(names = {"-v", "--verbose"}, description = "verbose output")
protected boolean verbose = false;
......@@ -164,6 +175,9 @@ public class JadxCLIArgs {
args.setExportAsGradleProject(exportAsGradleProject);
args.setUseImports(useImports);
args.setDebugInfo(debugInfo);
args.setRenameCaseSensitive(isRenameCaseSensitive());
args.setRenameValid(isRenameValid());
args.setRenamePrintable(isRenamePrintable());
return args;
}
......@@ -254,4 +268,73 @@ public class JadxCLIArgs {
public boolean isExportAsGradleProject() {
return exportAsGradleProject;
}
public boolean isRenameCaseSensitive() {
return renameFlags.contains(RENAME.CASE);
}
public void setRenameCaseSensitive(boolean renameCase) {
if (renameCase && !isRenameCaseSensitive()) {
renameFlags.add(RENAME.CASE);
} else if (!renameCase && isRenameCaseSensitive()) {
renameFlags.remove(RENAME.CASE);
}
}
public boolean isRenameValid() {
return renameFlags.contains(RENAME.VALID);
}
public void setRenameValid(boolean renameValid) {
if (renameValid && !isRenameValid()) {
renameFlags.add(RENAME.VALID);
} else if (!renameValid && isRenameValid()) {
renameFlags.remove(RENAME.VALID);
}
}
public boolean isRenamePrintable() {
return renameFlags.contains(RENAME.PRINTABLE);
}
public void setRenamePrintable(boolean renamePrintable) {
if (renamePrintable && !isRenamePrintable()) {
renameFlags.add(RENAME.PRINTABLE);
} else if (!renamePrintable && isRenamePrintable()) {
renameFlags.remove(RENAME.PRINTABLE);
}
}
static class RenameConverter implements IStringConverter<Set<RENAME>> {
private final String paramName;
RenameConverter(String paramName) {
this.paramName = paramName;
}
@Override
public Set<RENAME> convert(String value) {
Set<RENAME> set = new HashSet<>();
if (value.equalsIgnoreCase("ALL")) {
set.add(RENAME.CASE);
set.add(RENAME.VALID);
set.add(RENAME.PRINTABLE);
} else if (!value.equalsIgnoreCase("NONE")) {
for (String s : value.split(",")) {
try {
set.add(RENAME.valueOf(s.toUpperCase(Locale.ROOT)));
} catch (IllegalArgumentException e) {
String values = "'" + RENAME.CASE
+ "', '" + RENAME.VALID
+ "' and '" + RENAME.PRINTABLE + '\'';
throw new IllegalArgumentException(
s + " is unknown for parameter " + paramName
+ ", possible values are " + values.toLowerCase(Locale.ROOT));
}
}
}
return set;
}
}
}
package jadx.cli;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Set;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import jadx.api.JadxArgs.RENAME;
import jadx.cli.JadxCLIArgs.RenameConverter;
public class RenameConverterTest {
private RenameConverter converter;
@BeforeEach
public void init() {
converter = new RenameConverter("someParam");
}
@Test
public void all() {
Set<RENAME> set = converter.convert("all");
assertEquals(3, set.size());
assertTrue(set.contains(RENAME.CASE));
assertTrue(set.contains(RENAME.VALID));
assertTrue(set.contains(RENAME.PRINTABLE));
}
@Test
public void none() {
Set<RENAME> set = converter.convert("none");
assertTrue(set.isEmpty());
}
@Test
public void wrong() {
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class,
() -> converter.convert("wrong"),
"Expected convert() to throw, but it didn't");
assertEquals("wrong is unknown for parameter someParam, "
+ "possible values are 'case', 'valid' and 'printable'",
thrown.getMessage());
}
}
......@@ -3,7 +3,9 @@ package jadx.api;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
public class JadxArgs {
......@@ -30,11 +32,11 @@ public class JadxArgs {
private boolean useImports = true;
private boolean debugInfo = true;
private boolean isSkipResources = false;
private boolean isSkipSources = false;
private boolean skipResources = false;
private boolean skipSources = false;
private boolean isDeobfuscationOn = false;
private boolean isDeobfuscationForceSave = false;
private boolean deobfuscationOn = false;
private boolean deobfuscationForceSave = false;
private boolean useSourceNameAsClassAlias = false;
private int deobfuscationMinLength = 0;
......@@ -45,7 +47,11 @@ public class JadxArgs {
private boolean respectBytecodeAccModifiers = false;
private boolean exportAsGradleProject = false;
private boolean isFsCaseSensitive;
private boolean fsCaseSensitive;
public enum RENAME {CASE, VALID, PRINTABLE}
private Set<RENAME> renameFlags = EnumSet.allOf(RENAME.class);
public JadxArgs() {
// use default options
......@@ -150,35 +156,35 @@ public class JadxArgs {
}
public boolean isSkipResources() {
return isSkipResources;
return skipResources;
}
public void setSkipResources(boolean skipResources) {
isSkipResources = skipResources;
this.skipResources = skipResources;
}
public boolean isSkipSources() {
return isSkipSources;
return skipSources;
}
public void setSkipSources(boolean skipSources) {
isSkipSources = skipSources;
this.skipSources = skipSources;
}
public boolean isDeobfuscationOn() {
return isDeobfuscationOn;
return deobfuscationOn;
}
public void setDeobfuscationOn(boolean deobfuscationOn) {
isDeobfuscationOn = deobfuscationOn;
this.deobfuscationOn = deobfuscationOn;
}
public boolean isDeobfuscationForceSave() {
return isDeobfuscationForceSave;
return deobfuscationForceSave;
}
public void setDeobfuscationForceSave(boolean deobfuscationForceSave) {
isDeobfuscationForceSave = deobfuscationForceSave;
this.deobfuscationForceSave = deobfuscationForceSave;
}
public boolean isUseSourceNameAsClassAlias() {
......@@ -238,11 +244,47 @@ public class JadxArgs {
}
public boolean isFsCaseSensitive() {
return isFsCaseSensitive;
return fsCaseSensitive;
}
public void setFsCaseSensitive(boolean fsCaseSensitive) {
isFsCaseSensitive = fsCaseSensitive;
this.fsCaseSensitive = fsCaseSensitive;
}
public boolean isRenameCaseSensitive() {
return renameFlags.contains(RENAME.CASE);
}
public void setRenameCaseSensitive(boolean renameCaseSensitive) {
if (renameCaseSensitive && !isRenameCaseSensitive()) {
renameFlags.add(RENAME.CASE);
} else if (!renameCaseSensitive && isRenameCaseSensitive()) {
renameFlags.remove(RENAME.CASE);
}
}
public boolean isRenameValid() {
return renameFlags.contains(RENAME.VALID);
}
public void setRenameValid(boolean renameValid) {
if (renameValid && !isRenameValid()) {
renameFlags.add(RENAME.VALID);
} else if (!renameValid && isRenameValid()) {
renameFlags.remove(RENAME.VALID);
}
}
public boolean isRenamePrintable() {
return renameFlags.contains(RENAME.PRINTABLE);
}
public void setRenamePrintable(boolean renamePrintable) {
if (renamePrintable && !isRenamePrintable()) {
renameFlags.add(RENAME.PRINTABLE);
} else if (!renamePrintable && isRenamePrintable()) {
renameFlags.remove(RENAME.PRINTABLE);
}
}
@Override
......@@ -257,10 +299,10 @@ public class JadxArgs {
", fallbackMode=" + fallbackMode +
", showInconsistentCode=" + showInconsistentCode +
", useImports=" + useImports +
", isSkipResources=" + isSkipResources +
", isSkipSources=" + isSkipSources +
", isDeobfuscationOn=" + isDeobfuscationOn +
", isDeobfuscationForceSave=" + isDeobfuscationForceSave +
", skipResources=" + skipResources +
", skipSources=" + skipSources +
", deobfuscationOn=" + deobfuscationOn +
", deobfuscationForceSave=" + deobfuscationForceSave +
", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias +
", deobfuscationMinLength=" + deobfuscationMinLength +
", deobfuscationMaxLength=" + deobfuscationMaxLength +
......@@ -268,6 +310,8 @@ public class JadxArgs {
", replaceConsts=" + replaceConsts +
", respectBytecodeAccModifiers=" + respectBytecodeAccModifiers +
", exportAsGradleProject=" + exportAsGradleProject +
", fsCaseSensitive=" + fsCaseSensitive +
", renameFlags=" + renameFlags +
'}';
}
}
......@@ -80,15 +80,13 @@ public class NameMapper {
public static boolean isValidIdentifier(String str) {
return notEmpty(str)
&& !isReserved(str)
&& VALID_JAVA_IDENTIFIER.matcher(str).matches()
&& isAllCharsPrintable(str);
&& VALID_JAVA_IDENTIFIER.matcher(str).matches();
}
public static boolean isValidFullIdentifier(String str) {
return notEmpty(str)
&& !isReserved(str)
&& VALID_JAVA_FULL_IDENTIFIER.matcher(str).matches()
&& isAllCharsPrintable(str);
&& VALID_JAVA_FULL_IDENTIFIER.matcher(str).matches();
}
public static boolean isValidIdentifierStart(int codePoint) {
......
......@@ -43,17 +43,17 @@ public class RenameVisitor extends AbstractVisitor {
if (deobfuscationOn) {
deobfuscator.execute();
}
checkClasses(root, args.isFsCaseSensitive());
checkClasses(root, args);
}
private void checkClasses(RootNode root, boolean caseSensitive) {
private void checkClasses(RootNode root, JadxArgs args) {
List<ClassNode> classes = root.getClasses(true);
for (ClassNode cls : classes) {
checkClassName(cls);
checkFields(cls);
checkMethods(cls);
checkClassName(cls, args);
checkFields(cls, args);
checkMethods(cls, args);
}
if (!caseSensitive) {
if (!args.isFsCaseSensitive() && args.isRenameCaseSensitive()) {
Set<String> clsFullPaths = new HashSet<>(classes.size());
for (ClassNode cls : classes) {
ClassInfo clsInfo = cls.getClassInfo();
......@@ -69,12 +69,12 @@ public class RenameVisitor extends AbstractVisitor {
}
}
private void checkClassName(ClassNode cls) {
private void checkClassName(ClassNode cls, JadxArgs args) {
ClassInfo classInfo = cls.getClassInfo();
ClassInfo alias = classInfo.getAlias();
String clsName = alias.getShortName();
String newShortName = fixClsShortName(clsName);
String newShortName = fixClsShortName(args, clsName);
if (!newShortName.equals(clsName)) {
classInfo.rename(cls.root(), alias.makeFullClsName(newShortName, true));
alias = classInfo.getAlias();
......@@ -86,35 +86,42 @@ public class RenameVisitor extends AbstractVisitor {
}
}
private String fixClsShortName(String clsName) {
private String fixClsShortName(JadxArgs args, String clsName) {
char firstChar = clsName.charAt(0);
if (Character.isDigit(firstChar)) {
boolean renameValid = args.isRenameValid();
if (Character.isDigit(firstChar) && renameValid) {
return Consts.ANONYMOUS_CLASS_PREFIX + NameMapper.removeInvalidCharsMiddle(clsName);
}
if (firstChar == '$') {
if (firstChar == '$' && renameValid) {
return 'C' + NameMapper.removeInvalidCharsMiddle(clsName);
}
String cleanClsName = NameMapper.removeInvalidChars(clsName, "C");
if (!NameMapper.isValidIdentifier(cleanClsName)) {
String cleanClsName = args.isRenamePrintable()
? NameMapper.removeInvalidChars(clsName, "C")
: clsName;
if (renameValid && !NameMapper.isValidIdentifier(cleanClsName)) {
return 'C' + cleanClsName;
}
return cleanClsName;
}
private void checkFields(ClassNode cls) {
private void checkFields(ClassNode cls, JadxArgs args) {
Set<String> names = new HashSet<>();
for (FieldNode field : cls.getFields()) {
FieldInfo fieldInfo = field.getFieldInfo();
String fieldName = fieldInfo.getAlias();
if (!names.add(fieldName) || !NameMapper.isValidIdentifier(fieldName)) {
if (!names.add(fieldName)
|| (args.isRenameValid() && !NameMapper.isValidIdentifier(fieldName))
|| (args.isRenamePrintable() && !NameMapper.isAllCharsPrintable(fieldName))) {
deobfuscator.forceRenameField(field);
}
}
}
private void checkMethods(ClassNode cls) {
private void checkMethods(ClassNode cls, JadxArgs args) {
for (MethodNode mth : cls.getMethods()) {
if (!NameMapper.isValidIdentifier(mth.getAlias())) {
String alias = mth.getAlias();
if (args.isRenameValid() && !NameMapper.isValidIdentifier(alias)
|| (args.isRenamePrintable() && !NameMapper.isAllCharsPrintable(alias))) {
deobfuscator.forceRenameMethod(mth);
}
}
......
package jadx.gui.settings;
import javax.swing.*;
import java.awt.*;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ItemEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Arrays;
import java.util.Collection;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSpinner;
import javax.swing.ScrollPaneConstants;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingConstants;
import javax.swing.WindowConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import say.swing.JFontChooser;
import jadx.gui.ui.MainWindow;
import jadx.gui.ui.codearea.EditorTheme;
import jadx.gui.utils.LangLocale;
import jadx.gui.utils.NLS;
import jadx.gui.utils.Utils;
import say.swing.JFontChooser;
public class JadxSettingsWindow extends JDialog {
private static final long serialVersionUID = -1804570470377354148L;
......@@ -51,6 +74,7 @@ public class JadxSettingsWindow extends JDialog {
panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
panel.add(makeDeobfuscationGroup());
panel.add(makeRenameGroup());
panel.add(makeDecompilationGroup());
panel.add(makeProjectGroup());
panel.add(makeEditorGroup());
......@@ -165,6 +189,35 @@ public class JadxSettingsWindow extends JDialog {
return deobfGroup;
}
private SettingsGroup makeRenameGroup() {
JCheckBox renameCaseSensitive = new JCheckBox();
renameCaseSensitive.setSelected(settings.isRenameCaseSensitive());
renameCaseSensitive.addItemListener(e -> {
settings.setRenameCaseSensitive(e.getStateChange() == ItemEvent.SELECTED);
needReload();
});
JCheckBox renameValid = new JCheckBox();
renameValid.setSelected(settings.isRenameValid());
renameValid.addItemListener(e -> {
settings.setRenameValid(e.getStateChange() == ItemEvent.SELECTED);
needReload();
});
JCheckBox renamePrintable = new JCheckBox();
renamePrintable.setSelected(settings.isRenamePrintable());
renamePrintable.addItemListener(e -> {
settings.setRenamePrintable(e.getStateChange() == ItemEvent.SELECTED);
needReload();
});
SettingsGroup group = new SettingsGroup(NLS.str("preferences.rename"));
group.addRow(NLS.str("preferences.rename_case"), renameCaseSensitive);
group.addRow(NLS.str("preferences.rename_valid"), renameValid);
group.addRow(NLS.str("preferences.rename_printable"), renamePrintable);
return group;
}
private void enableComponentList(Collection<JComponent> connectedComponents, boolean enabled) {
connectedComponents.forEach(comp -> comp.setEnabled(enabled));
}
......
......@@ -166,6 +166,7 @@ public class MainWindow extends JFrame {
setLocationRelativeTo(null);
setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
closeWindow();
}
......@@ -386,6 +387,16 @@ public class MainWindow extends JFrame {
}
private void saveAll(boolean export) {
JadxArgs decompilerArgs = wrapper.getArgs();
if ((!decompilerArgs.isFsCaseSensitive() && !decompilerArgs.isRenameCaseSensitive())
|| !decompilerArgs.isRenameValid() || !decompilerArgs.isRenamePrintable()) {
JOptionPane.showMessageDialog(
this,
NLS.str("msg.rename_disabled", settings.getLangLocale()),
NLS.str("msg.rename_disabled_title", settings.getLangLocale()),
JOptionPane.INFORMATION_MESSAGE
);
}
JFileChooser fileChooser = new JFileChooser();
fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
fileChooser.setToolTipText(NLS.str("file.save_all_msg"));
......@@ -397,7 +408,6 @@ public class MainWindow extends JFrame {
int ret = fileChooser.showSaveDialog(mainPanel);
if (ret == JFileChooser.APPROVE_OPTION) {
JadxArgs decompilerArgs = wrapper.getArgs();
decompilerArgs.setExportAsGradleProject(export);
if (export) {
decompilerArgs.setSkipSources(false);
......
......@@ -113,6 +113,10 @@ preferences.cancel=Cancel
preferences.reset=Reset
preferences.reset_message=Reset settings to default values?
preferences.reset_title=Reset settings
preferences.rename=Rename
preferences.rename_case=System case sensitivity
preferences.rename_valid=To be valid identifier
preferences.rename_printable=To be printable
msg.open_file=Please open file
msg.saving_sources=Saving sources...
......@@ -121,6 +125,8 @@ msg.language_changed=New language will be displayed the next time application st
msg.index_not_initialized=Index not initialized, search will be disabled!
msg.project_error_title=Error
msg.project_error=Project could not be loaded
msg.rename_disabled_title=Rename disabled
msg.rename_disabled=Some of rename settings are disabled, please take this into consideration
popup.undo=Undo
popup.redo=Redo
......
......@@ -113,6 +113,10 @@ preferences.cancel=Cancelar
preferences.reset=Reestablecer
preferences.reset_message=¿Reestablecer preferencias a valores por defecto?
preferences.reset_title=Reestablecer preferencias
#preferences.rename=
#preferences.rename_case=
#preferences.rename_valid=
#preferences.rename_printable=
msg.open_file=Por favor, abra un archivo
msg.saving_sources=Guardando fuente...
......@@ -121,6 +125,8 @@ msg.language_changed=El nuevo idioma se mostrará la próxima vez que la aplicac
msg.index_not_initialized=Índice no inicializado, ¡la bósqueda se desactivará!
#msg.project_error_title=
#msg.project_error=
#msg.rename_disabled_title=
#msg.rename_disabled=
popup.undo=Deshacer
popup.redo=Rehacer
......
......@@ -113,6 +113,10 @@ preferences.cancel=取消
preferences.reset=重置
preferences.reset_message=要恢复默认设置吗?
preferences.reset_title=重置设置
#preferences.rename=
#preferences.rename_case=
#preferences.rename_valid=
#preferences.rename_printable=
msg.open_file=请打开文件
msg.saving_sources=正在导出源代码...
......@@ -121,6 +125,8 @@ msg.language_changed=在下次启动时将会显示新的语言。
msg.index_not_initialized=索引尚未初始化,无法进行搜索!
#msg.project_error_title=
#msg.project_error=
#msg.rename_disabled_title=
#msg.rename_disabled=
popup.undo=撤销
popup.redo=重做
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment