From a0a9f7fd4129c2bba8bc90c1e6d8b436731e406f Mon Sep 17 00:00:00 2001 From: Yaroslav <43380144+MrIkso@users.noreply.github.com> Date: Fri, 23 May 2025 01:05:05 +0300 Subject: [PATCH] feat(gui): notify user if code has non-displayable character with current font (PR #2490) * feat(gui): notify user, if code has non-displayable character in current font (fix #621) * fix(gui): improve check showing undisplayed chars on current font * fix code style --------- Co-authored-by: Skylot <118523+skylot@users.noreply.github.com> --- .../gui/settings/ui/font/FontChooserHack.java | 10 +++ .../src/main/java/jadx/gui/ui/MainWindow.java | 61 ++++++++++++++++ .../gui/ui/panel/UndisplayedStringsPanel.java | 73 +++++++++++++++++++ .../ui/treenodes/UndisplayedStringsNode.java | 51 +++++++++++++ .../main/java/jadx/gui/utils/FontUtils.java | 15 ++++ .../src/main/java/jadx/gui/utils/Icons.java | 2 + .../resources/i18n/Messages_de_DE.properties | 4 +- .../resources/i18n/Messages_en_US.properties | 2 + .../resources/i18n/Messages_es_ES.properties | 2 + .../resources/i18n/Messages_id_ID.properties | 2 + .../resources/i18n/Messages_ko_KR.properties | 2 + .../resources/i18n/Messages_pt_BR.properties | 2 + .../resources/i18n/Messages_ru_RU.properties | 2 + .../resources/i18n/Messages_zh_CN.properties | 2 + .../resources/i18n/Messages_zh_TW.properties | 2 + 15 files changed, 231 insertions(+), 1 deletion(-) create mode 100644 jadx-gui/src/main/java/jadx/gui/ui/panel/UndisplayedStringsPanel.java create mode 100644 jadx-gui/src/main/java/jadx/gui/ui/treenodes/UndisplayedStringsNode.java diff --git a/jadx-gui/src/main/java/jadx/gui/settings/ui/font/FontChooserHack.java b/jadx-gui/src/main/java/jadx/gui/settings/ui/font/FontChooserHack.java index fd496104b..1f44ef8bd 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/ui/font/FontChooserHack.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/ui/font/FontChooserHack.java @@ -3,6 +3,7 @@ package jadx.gui.settings.ui.font; import java.lang.reflect.Field; import javax.swing.JCheckBox; +import javax.swing.JPanel; import org.drjekyll.fontchooser.FontChooser; import org.drjekyll.fontchooser.panes.FamilyPane; @@ -23,6 +24,15 @@ public class FontChooserHack { } } + public static void hidePreview(FontChooser fontChooser) { + try { + JPanel previewPanel = (JPanel) getPrivateField(fontChooser, "previewPanel"); + previewPanel.setVisible(false); + } catch (Throwable e) { + LOG.debug("Failed to hide preview panel", e); + } + } + private static Object getPrivateField(Object obj, String fieldName) throws NoSuchFieldException, IllegalAccessException { Field f = obj.getClass().getDeclaredField(fieldName); f.setAccessible(true); 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 1a383863f..5cc9490ae 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java @@ -85,6 +85,9 @@ import jadx.api.plugins.events.types.ReloadProject; import jadx.api.plugins.events.types.ReloadSettingsWindow; import jadx.api.plugins.utils.CommonFileUtils; import jadx.core.Jadx; +import jadx.core.dex.nodes.ClassNode; +import jadx.core.dex.nodes.FieldNode; +import jadx.core.dex.nodes.MethodNode; import jadx.core.export.TemplateFile; import jadx.core.utils.ListUtils; import jadx.core.utils.StringUtils; @@ -160,6 +163,7 @@ import jadx.gui.ui.tab.TabsController; import jadx.gui.ui.tab.dnd.TabDndController; import jadx.gui.ui.treenodes.StartPageNode; import jadx.gui.ui.treenodes.SummaryNode; +import jadx.gui.ui.treenodes.UndisplayedStringsNode; import jadx.gui.update.JadxUpdate; import jadx.gui.utils.CacheObject; import jadx.gui.utils.DesktopEntryUtils; @@ -240,6 +244,7 @@ public class MainWindow extends JFrame { private final List> treeUpdateListener = new ArrayList<>(); private boolean loaded; private boolean settingsOpen = false; + private boolean showUndisplayedCharsDialog; private final ShortcutsController shortcutsController; private JadxMenuBar menuBar; @@ -506,6 +511,7 @@ public class MainWindow extends JFrame { // start new project project = new JadxProject(this); project.setFilePaths(paths); + showUndisplayedCharsDialog = false; loadFiles(onFinish); } @@ -654,6 +660,7 @@ public class MainWindow extends JFrame { runInitialBackgroundJobs(); notifyLoadListeners(true); update(); + checkIfCodeHasNonPrintableChars(); }); } @@ -1794,6 +1801,60 @@ public class MainWindow extends JFrame { } } + private void checkIfCodeHasNonPrintableChars() { + if (getSettings().isRenamePrintable() || getSettings().isDeobfuscationOn()) { + return; + } + + if (showUndisplayedCharsDialog) { + return; + } + + StringBuilder nonDisplayString = new StringBuilder(); + + List classes = wrapper.getRootNode().getClasses(true); + Font font = getSettings().getFont(); + boolean hasNonDisplayable = false; + + for (ClassNode cls : classes) { + String className = cls.getRawName(); + if (!FontUtils.canStringBeDisplayed(className, font)) { + hasNonDisplayable = true; + nonDisplayString.append(className); + nonDisplayString.append("\n"); + } + + for (MethodNode methodNode : cls.getMethods()) { + String methodName = methodNode.getName(); + if (!FontUtils.canStringBeDisplayed(methodName, font)) { + hasNonDisplayable = true; + nonDisplayString.append(methodName); + nonDisplayString.append("\n"); + } + } + + for (FieldNode fieldNode : cls.getFields()) { + String fieldName = fieldNode.getName(); + if (!FontUtils.canStringBeDisplayed(fieldName, font)) { + hasNonDisplayable = true; + nonDisplayString.append(fieldName); + nonDisplayString.append("\n"); + } + } + } + + if (hasNonDisplayable) { + showUndisplayedCharsDialog = true; + int dialogResult = JOptionPane.showConfirmDialog(this, + NLS.str("msg.non_displayable_chars", font.getFontName()), + NLS.str("msg.warning_title"), + JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); + if (dialogResult == JOptionPane.YES_OPTION) { + tabsController.selectTab(new UndisplayedStringsNode(nonDisplayString.toString())); + } + } + } + public RenameMappingsGui getRenameMappings() { return renameMappings; } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/panel/UndisplayedStringsPanel.java b/jadx-gui/src/main/java/jadx/gui/ui/panel/UndisplayedStringsPanel.java new file mode 100644 index 000000000..693bc9bc5 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/ui/panel/UndisplayedStringsPanel.java @@ -0,0 +1,73 @@ +package jadx.gui.ui.panel; + +import java.awt.BorderLayout; +import java.awt.Font; + +import javax.swing.BorderFactory; +import javax.swing.SwingUtilities; + +import org.drjekyll.fontchooser.FontChooser; +import org.drjekyll.fontchooser.model.FontSelectionModel; +import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; +import org.fife.ui.rtextarea.RTextScrollPane; + +import jadx.gui.settings.JadxSettings; +import jadx.gui.settings.LineNumbersMode; +import jadx.gui.settings.ui.font.FontChooserHack; +import jadx.gui.ui.codearea.AbstractCodeArea; +import jadx.gui.ui.tab.TabbedPane; +import jadx.gui.ui.treenodes.UndisplayedStringsNode; + +public class UndisplayedStringsPanel extends ContentPanel { + private static final long serialVersionUID = 695370628262996993L; + + private final RSyntaxTextArea textPane; + private final RTextScrollPane codeScrollPane; + + public UndisplayedStringsPanel(TabbedPane panel, UndisplayedStringsNode node) { + super(panel, node); + setLayout(new BorderLayout()); + textPane = AbstractCodeArea.getDefaultArea(panel.getMainWindow()); + + JadxSettings settings = getSettings(); + Font selectedFont = settings.getFont(); + + FontChooser fontChooser = new FontChooser(); + fontChooser.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); + fontChooser.setSelectedFont(selectedFont); + FontChooserHack.hidePreview(fontChooser); + + fontChooser.addChangeListener(event -> { + FontSelectionModel model = (FontSelectionModel) event.getSource(); + settings.setFont(model.getSelectedFont()); + SwingUtilities.invokeLater(() -> { + getMainWindow().loadSettings(); + }); + }); + + codeScrollPane = new RTextScrollPane(textPane); + + add(codeScrollPane, BorderLayout.CENTER); + add(fontChooser, BorderLayout.EAST); + + applySettings(); + showData(node.makeDescString()); + } + + private void applySettings() { + codeScrollPane.setLineNumbersEnabled(getSettings().getLineNumbersMode() != LineNumbersMode.DISABLE); + codeScrollPane.getGutter().setLineNumberFont(getSettings().getFont()); + textPane.setFont(getSettings().getFont()); + } + + private void showData(String data) { + textPane.setText(data); + textPane.setCaretPosition(0); + } + + @Override + public void loadSettings() { + applySettings(); + } + +} diff --git a/jadx-gui/src/main/java/jadx/gui/ui/treenodes/UndisplayedStringsNode.java b/jadx-gui/src/main/java/jadx/gui/ui/treenodes/UndisplayedStringsNode.java new file mode 100644 index 000000000..2b5edbb28 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/ui/treenodes/UndisplayedStringsNode.java @@ -0,0 +1,51 @@ +package jadx.gui.ui.treenodes; + +import javax.swing.*; + +import jadx.gui.treemodel.JClass; +import jadx.gui.treemodel.JNode; +import jadx.gui.ui.panel.ContentPanel; +import jadx.gui.ui.panel.UndisplayedStringsPanel; +import jadx.gui.ui.tab.TabbedPane; +import jadx.gui.utils.Icons; +import jadx.gui.utils.NLS; + +public class UndisplayedStringsNode extends JNode { + private static final long serialVersionUID = 2005158949697898302L; + + private final String undisplayedStings; + + public UndisplayedStringsNode(String undisplayedStings) { + this.undisplayedStings = undisplayedStings; + } + + @Override + public ContentPanel getContentPanel(TabbedPane tabbedPane) { + return new UndisplayedStringsPanel(tabbedPane, this); + } + + @Override + public String makeString() { + return NLS.str("msg.non_displayable_chars.title"); + } + + @Override + public Icon getIcon() { + return Icons.FONT; + } + + @Override + public JClass getJParent() { + return null; + } + + @Override + public String makeDescString() { + return undisplayedStings; + } + + @Override + public boolean supportsQuickTabs() { + return false; + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/utils/FontUtils.java b/jadx-gui/src/main/java/jadx/gui/utils/FontUtils.java index 417ed01b4..4773ecde6 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/FontUtils.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/FontUtils.java @@ -87,6 +87,21 @@ public class FontUtils { } } + public static boolean canStringBeDisplayed(String str, Font font) { + if (str == null || str.isEmpty()) { + return true; + } + int offset = 0; + while (offset < str.length()) { + int codePoint = str.codePointAt(offset); + if (!font.canDisplay(codePoint)) { + return false; + } + offset += Character.charCount(codePoint); + } + return true; + } + private FontUtils() { } } diff --git a/jadx-gui/src/main/java/jadx/gui/utils/Icons.java b/jadx-gui/src/main/java/jadx/gui/utils/Icons.java index 2f2975958..ec513d502 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/Icons.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/Icons.java @@ -46,4 +46,6 @@ public class Icons { public static final ImageIcon CHECK = UiUtils.openSvgIcon("ui/checkConstraint"); public static final ImageIcon FORMAT = UiUtils.openSvgIcon("ui/toolWindowMessages"); public static final ImageIcon RESET = UiUtils.openSvgIcon("ui/reset"); + + public static final ImageIcon FONT = UiUtils.openSvgIcon("nodes/fontFile"); } 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 e3f8b767e..8236fdb63 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties @@ -184,7 +184,7 @@ search_dialog.copy=alles kopieren usage_dialog.title=Verwendungssuche usage_dialog.label=Verwendungen von: -#usage_dialog_plus.title=Verwendungssuche +#usage_dialog_plus.title=Usage tree search usage_dialog_plus.jump_to=Zur aktuellen Position springen usage_dialog_plus.copy_path=Verwendungspfad kopieren usage_dialog_plus.search_complete=Suche abgeschlossen @@ -343,6 +343,8 @@ msg.project_error_title=Fehler msg.project_error=Projekt konnte nicht geladen werden msg.cmd_select_class_error=Klasse\n%s auswählen nicht möglich\nSie existiert nicht. msg.cant_add_comment=Kann hier keinen Kommentar hinzufügen +#msg.non_displayable_chars=Some characters were found in code are not displayable by currently used font "%s". Show chars? +#msg.non_displayable_chars.title=Undisplayed Strings #methods_dialog.title=Select methods 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 edd6bd30b..1d5c9fc38 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties @@ -343,6 +343,8 @@ msg.project_error_title=Error msg.project_error=Project could not be loaded msg.cmd_select_class_error=Failed to select the class\n%s\nThe class does not exist. msg.cant_add_comment=Can't add comment here +msg.non_displayable_chars=Some characters were found in code are not displayable by currently used font "%s". Show chars? +msg.non_displayable_chars.title=Undisplayed Strings methods_dialog.title=Select methods 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 763768ebd..70a632660 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties @@ -343,6 +343,8 @@ msg.language_changed=El nuevo idioma se mostrará la próxima vez que la aplicac #msg.project_error=Project could not be loaded #msg.cmd_select_class_error=Failed to select the class\n%s\nThe class does not exist. #msg.cant_add_comment=Can't add comment here +#msg.non_displayable_chars=Some characters were found in code are not displayable by currently used font "%s". Show chars? +#msg.non_displayable_chars.title=Undisplayed Strings #methods_dialog.title=Select methods diff --git a/jadx-gui/src/main/resources/i18n/Messages_id_ID.properties b/jadx-gui/src/main/resources/i18n/Messages_id_ID.properties index 983ef60ea..cd2e8ed0f 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_id_ID.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_id_ID.properties @@ -343,6 +343,8 @@ msg.project_error_title=Kesalahan msg.project_error=Proyek tidak dapat dimuat msg.cmd_select_class_error=Gagal memilih kelas\n%s\nKelas tidak ada. msg.cant_add_comment=Tidak dapat menambahkan komentar di sini +#msg.non_displayable_chars=Some characters were found in code are not displayable by currently used font "%s". Show chars? +#msg.non_displayable_chars.title=Undisplayed Strings #methods_dialog.title=Select methods 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 0820176c5..44360e4b1 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties @@ -343,6 +343,8 @@ msg.project_error_title=오류 msg.project_error=프로젝트를 로드 할 수 없습니다. msg.cmd_select_class_error=클래스를 선택하지 못했습니다.\n%s\n클래스가 없습니다. msg.cant_add_comment=여기에 주석을 추가할수 없음 +#msg.non_displayable_chars=Some characters were found in code are not displayable by currently used font "%s". Show chars? +#msg.non_displayable_chars.title=Undisplayed Strings #methods_dialog.title=Select methods diff --git a/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties b/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties index 8f4fd6805..877e73403 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties @@ -343,6 +343,8 @@ msg.project_error_title=Erro msg.project_error=Projeto não pôde ser carregado msg.cmd_select_class_error=Falha ao selecionar classe\n%s\nA classe não existe. msg.cant_add_comment=Não é possível adicionar comentários aqui +#msg.non_displayable_chars=Some characters were found in code are not displayable by currently used font "%s". Show chars? +#msg.non_displayable_chars.title=Undisplayed Strings #methods_dialog.title=Select methods diff --git a/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties b/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties index 1fe3552d7..3f20e4ce0 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties @@ -343,6 +343,8 @@ msg.project_error_title=Ошибка msg.project_error=Проект не может быть загружен msg.cmd_select_class_error=Ошибка выбора класса\n%s\nЭтот класс не существует. msg.cant_add_comment=Невозможно добавить комментарий сюда +#msg.non_displayable_chars=Some characters were found in code are not displayable by currently used font "%s". Show chars? +#msg.non_displayable_chars.title=Undisplayed Strings #methods_dialog.title=Select methods 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 c059790c1..608ce05de 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties @@ -343,6 +343,8 @@ msg.project_error_title=错误 msg.project_error=项目无法加载 msg.cmd_select_class_error=无法选择类\n%s\n该类不存在。 msg.cant_add_comment=无法在此添加注释 +#msg.non_displayable_chars=Some characters were found in code are not displayable by currently used font "%s". Show chars? +#msg.non_displayable_chars.title=Undisplayed Strings #methods_dialog.title=Select methods 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 c86f733e4..08a1ec6a4 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties @@ -343,6 +343,8 @@ msg.project_error_title=錯誤 msg.project_error=無法載入專案 msg.cmd_select_class_error=無法選擇類別\n%s\n類別不存在。 msg.cant_add_comment=無法在此新增註解 +#msg.non_displayable_chars=Some characters were found in code are not displayable by currently used font "%s". Show chars? +#msg.non_displayable_chars.title=Undisplayed Strings #methods_dialog.title=Select methods