Merge remote-tracking branch

'origin/GP-5689_dev747368_byteviewer_charset_column--SQUASHED'
This commit is contained in:
Ryan Kurtz
2026-01-06 04:45:36 -05:00
92 changed files with 7990 additions and 4331 deletions

View File

@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -16,7 +16,6 @@
package ghidra.app.plugin.core.debug.gui.memory;
import java.awt.Color;
import java.awt.FontMetrics;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
@@ -118,9 +117,8 @@ public class DebuggerMemoryByteViewerComponent extends ByteViewerComponent
private final List<SelectionGenerator> selectionGenerators;
public DebuggerMemoryByteViewerComponent(DebuggerMemoryBytesPanel vpanel,
ByteViewerLayoutModel layoutModel, DataFormatModel model, int bytesPerLine,
FontMetrics fm) {
super(vpanel, layoutModel, model, bytesPerLine, fm);
ByteViewerLayoutModel layoutModel, DataFormatModel model, int bytesPerLine) {
super(vpanel, layoutModel, model, bytesPerLine);
// TODO: I don't care much for this reverse path
this.panel = vpanel;

View File

@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -37,6 +37,6 @@ public class DebuggerMemoryBytesPanel extends ByteViewerPanel {
@Override
protected ByteViewerComponent newByteViewerComponent(DataFormatModel model) {
return new DebuggerMemoryByteViewerComponent(this, new ByteViewerLayoutModel(), model,
getBytesPerLine(), getFontMetrics());
getBytesPerLine());
}
}

View File

@@ -279,6 +279,8 @@ src/main/help/help/topics/CParserPlugin/images/UseOpenArchives.png||GHIDRA||||EN
src/main/help/help/topics/CallTreePlugin/Call_Tree_Plugin.html||GHIDRA||||END|
src/main/help/help/topics/CallTreePlugin/images/CallTreeWindow.png||GHIDRA||||END|
src/main/help/help/topics/CallTreePlugin/images/depth-input.png||GHIDRA||reviewed||END|
src/main/help/help/topics/Charsets/Charsets.htm||GHIDRA||||END|
src/main/help/help/topics/Charsets/images/CharsetPickerDialog.png||GHIDRA||||END|
src/main/help/help/topics/ClearPlugin/Clear.htm||GHIDRA||||END|
src/main/help/help/topics/ClearPlugin/images/ClearFlow.png||GHIDRA||||END|
src/main/help/help/topics/ClearPlugin/images/ClearWithOptions.png||GHIDRA||||END|

View File

@@ -207,7 +207,7 @@ font.plugin.terminal.dim.fraktur = font.plugin.terminal.dim
font.plugin.terminal.completion.list = dialog-plain-12
font.plugin.window.location = font.monospaced[40]
font.textarea.astextfield = [laf.font]TextField.font
[Dark Defaults]

View File

@@ -236,6 +236,7 @@
<tocdef id="Translate Strings" sortgroup="c" text="Translate Strings" target="help/topics/TranslateStringsPlugin/TranslateStringsPlugin.htm">
<tocdef id="LibreTranslate" sortgroup="a" text="LibreTranslate" target="help/topics/LibreTranslatePlugin/LibreTranslatePlugin.htm" />
</tocdef>
<tocdef id="Charsets" sortgroup="c1" text="Charsets" target="help/topics/Charsets/Charsets.htm" />
<tocdef id="Save Image" sortgroup="d" text="Save Image" target="help/topics/ResourceActionsPlugin/ResourceActions.html" />
</tocdef>

View File

@@ -0,0 +1,96 @@
<!DOCTYPE doctype PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN">
<HTML>
<HEAD>
<TITLE>Charsets</TITLE>
<META http-equiv="Content-Type" content="text/html; charset=windows-1252">
<LINK rel="stylesheet" type="text/css" href="help/shared/DefaultStyle.css">
</HEAD>
<BODY lang="EN-US">
<H1><a name="Charsets"></a>Charsets</H1>
<P>Charsets are named mappings between byte sequences and Unicode code points.</P>
<p>Common examples:
<ul>
<li>US-ASCII</li>
<li>UTF-8</li>
<li>UTF-16</li>
</ul>
</p>
<h2><a name="UnicodeCodePoint"></a>Unicode Code Point</h2>
<p>The term "code point" denotes an integer that represents a character in the Unicode standard.
The first 127 code points map directly to the well-known ASCII characters.</p>
<p>There are approximately 1.1 million defined code points in the Unicode standard.</p>
<h2><a name="UnicodeScripts"></a>Unicode Scripts</h2>
<p>Unicode code points are grouped into categories called 'scripts'. For example, the Latin
script contains the well-known ABC..XYZ characters, the Common script contains numbers (0-9) and
punctuation, and the Greek script contains characters such as the delta '&#x394;' symbol.</p>
<p>Do not conflate a script (alphabet) with a human language, even though there can be some
correlation.</p>
<p>The Unicode standard that Java implements has about 160 different scripts.</p>
<h2><a name="CharsetPicker"></a>Charset Picker</h2>
<p>The charset picker dialog allows the user to browse the available installed charsets
and select one to be used when decoding strings / character data.</p>
<div style="text-align: center;">
<br>
<img src="images/CharsetPickerDialog.png">
<br>
</div>
<p>The dialog has a table with the following columns:</p>
<h3>Name</h3>
<p>Name of the charset. Charsets not in the IANA Charset Registry will have a "x-" prefix</p>
<h3>Description</h3>
<p>A short description of the charset.</p>
<h3>Fixed Length</h3>
<p>Flag to indicate that the charset always uses a fixed number of bytes to encode a character.</p>
<h3>Min BPC / Max BPC (Bytes Per Character)</h3>
<p>The minimum and maximum number of bytes that the charset uses to encoded a single character.
This value may include the length of escape sequences that typically only appear once in a
string.</p>
<h3>Scripts</h3>
<p>A list of the scripts (alphabets) that the charset can encode/decode characters for. For
example, US-ASCII can only produce Latin characters (along with ubiquitous Common characters),
whereas the UTF-8 charset can encoded characters for every script that the Unicode standard
supports.</p>
<p>In the Details panel, the <b>Scripts</b> list also can contain a short snippet of example
characters from that script (if your font supports the script in question).</p>
<h3>Charset <b>Details</b></h3>
<p>Below the dialog's table is an area that displays the details of the charset, which
in addition to the information displayed in the table contains:</p>
<h3>Aligned Size</h3>
<p>For charsets where the bytes of encoded characters should be treated as a larger-than-byte
integer type (because they would have been specified as arrays of those integer types), this
field will specify the size of that integer. Currently, only UTF-16 and UTF-32 specify a
value for this setting.</p>
<P class="relatedtopic">Related Topics:</P>
<UL>
<LI><A href="help/topics/Search/Search_for_Strings.htm">Search .. for Strings</A></LI>
<LI><A href="../Search/Search_for_Strings.htm#Encoded_Strings_Dialog">Search for Encoded Strings</A></LI>
<LI><A href="../TranslateStringsPlugin/TranslateStringsPlugin.htm">Translate Strings Plugin</A></LI>
<LI><A href="../DataPlugin/Data.htm#StringDataTypes">String data types</A></LI>
</UL>
<p></p>
</BODY>
</HTML>

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

@@ -58,6 +58,7 @@ import ghidra.program.model.util.CodeUnitInsertionException;
import ghidra.program.util.ProgramLocation;
import ghidra.util.Msg;
import ghidra.util.Swing;
import ghidra.util.charset.CharsetInfoManager;
import ghidra.util.exception.CancelledException;
import ghidra.util.layout.PairLayout;
import ghidra.util.table.GhidraTable;
@@ -69,10 +70,10 @@ public class EncodedStringsDialog extends DialogComponentProvider {
private static final Map<String, AbstractStringDataType> CHARSET_TO_DT_MAP = Map.ofEntries(
// charsets not in this map will use StringDataType and will
// set the charset setting at the memory location of the string to be created
Map.entry(CharsetInfo.USASCII, StringDataType.dataType),
Map.entry(CharsetInfo.UTF8, StringUTF8DataType.dataType),
Map.entry(CharsetInfo.UTF16, UnicodeDataType.dataType),
Map.entry(CharsetInfo.UTF32, Unicode32DataType.dataType));
Map.entry(CharsetInfoManager.USASCII, StringDataType.dataType),
Map.entry(CharsetInfoManager.UTF8, StringUTF8DataType.dataType),
Map.entry(CharsetInfoManager.UTF16, UnicodeDataType.dataType),
Map.entry(CharsetInfoManager.UTF32, Unicode32DataType.dataType));
private static final String BUTTON_FONT_ID = "font.plugin.strings.buttons";
@@ -443,7 +444,7 @@ public class EncodedStringsDialog extends DialogComponentProvider {
private void buildCharsetPickerComponents() {
charsetComboBox = new GhidraComboBox<>();
charsetComboBox.getAccessibleContext().setAccessibleName("Charset Checkboxes");
for (String charsetName : CharsetInfo.getInstance().getCharsetNames()) {
for (String charsetName : CharsetInfoManager.getInstance().getCharsetNames()) {
charsetComboBox.addToModel(charsetName);
}
charsetComboBox.setSelectedItem(getDefault(EncodedStringsPlugin.CHARSET_OPTIONNAME,
@@ -908,7 +909,7 @@ public class EncodedStringsDialog extends DialogComponentProvider {
private void updateOptions() {
String charsetName = charsetComboBox.getSelectedItem().toString();
if (!charsetExists(charsetName)) {
charsetName = CharsetInfo.USASCII;
charsetName = CharsetInfoManager.USASCII;
}
boolean scriptOptions = showScriptOptionsButton.isSelected();
@@ -930,7 +931,7 @@ public class EncodedStringsDialog extends DialogComponentProvider {
settings = new SettingsImpl();
CharsetSettingsDefinition.CHARSET.setCharset(settings, charsetName);
}
int charSize = CharsetInfo.getInstance().getCharsetCharSize(charsetName);
int charSize = CharsetInfoManager.getInstance().getCharsetCharSize(charsetName);
updateTrigramStringValidator(stringModelFilenameComboBox.getText());
boolean requireValidStrings = requireValidStringCB.isSelected();

View File

@@ -30,9 +30,9 @@ import ghidra.framework.plugintool.PluginInfo;
import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.data.CharsetInfo;
import ghidra.program.model.listing.Program;
import ghidra.util.HelpLocation;
import ghidra.util.charset.CharsetInfoManager;
import ghidra.util.datastruct.WeakDataStructureFactory;
import ghidra.util.datastruct.WeakSet;
@@ -52,7 +52,7 @@ public class EncodedStringsPlugin extends ProgramPlugin {
private static final String ACTIONNAME = "Search For Encoded Strings";
static final String STRINGS_OPTION_NAME = "Strings";
static final String CHARSET_OPTIONNAME = "Default Charset";
static final String CHARSET_DEFAULT_VALUE = CharsetInfo.USASCII;
static final String CHARSET_DEFAULT_VALUE = CharsetInfoManager.USASCII;
static final String TRANSLATE_SERVICE_OPTIONNAME = "Default Translation Service Name";
static final String STRINGMODEL_FILENAME_OPTIONNAME = "Default String Model Filename";
static final String STRINGMODEL_FILENAME_DEFAULT = "stringngrams/StringModel.sng";

View File

@@ -0,0 +1,161 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.util.charset.picker;
import java.awt.Font;
import java.lang.Character.UnicodeScript;
import java.util.*;
import javax.swing.*;
import docking.widgets.checkbox.GCheckBox;
import docking.widgets.label.GLabel;
import docking.widgets.list.GListCellRenderer;
import generic.theme.Gui;
import ghidra.app.plugin.core.strings.CharacterScriptUtils;
import ghidra.util.charset.CharsetInfo;
import ghidra.util.layout.VariableHeightPairLayout;
/**
* A JPanel that displays the details about a {@link CharsetInfo} object.
*/
public class CharsetInfoPanel extends JPanel {
private Map<UnicodeScript, String> scriptExampleStrings = new HashMap<>();
private JTextField nameTF;
private JTextArea commentTA;
private GCheckBox fixedCB;
private JTextField minmaxTF;
private JTextField alignTF;
private JList<UnicodeScript> scriptsList;
public CharsetInfoPanel() {
super(new VariableHeightPairLayout());
build();
}
private void build() {
nameTF = new JTextField();
nameTF.setEditable(false);
nameTF.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
add(newLabel("Name:", "Charset name", nameTF, false));
add(nameTF);
commentTA = new JTextArea(2, 100);
commentTA.setEditable(false);
commentTA.setLineWrap(true);
commentTA.setWrapStyleWord(true);
Gui.registerFont(commentTA, "font.textarea.astextfield");
add(newLabel("Description:", "Charset description", commentTA, true));
add(commentTA);
fixedCB = new GCheckBox();
fixedCB.setEnabled(false);
add(newLabel("Fixed Length:", "Charset uses a fixed number of bytes to produce a character",
fixedCB, false));
add(fixedCB);
minmaxTF = new JTextField();
minmaxTF.setEditable(false);
minmaxTF.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
add(newLabel("Min/Max Bytes Per Char:",
"Number of bytes a charset needs to produce a character", minmaxTF, false));
add(minmaxTF);
alignTF = new JTextField();
alignTF.setEditable(false);
alignTF.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
add(newLabel("Aligned Size:", "Byte offset that is valid to start a character", alignTF,
false));
add(alignTF);
scriptsList = new JList<>(List.of().toArray(UnicodeScript[]::new));
scriptsList.setCellRenderer(
GListCellRenderer.createDefaultTextRenderer(this::getScriptCellRendererText));
scriptsList.setVisibleRowCount(5);
JScrollPane scriptsSP = new JScrollPane();
scriptsSP.setFocusable(false);
scriptsSP.getVerticalScrollBar().setFocusable(false);
scriptsSP.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
scriptsSP.getViewport().add(scriptsList);
add(newLabel("Scripts:", "The scripts that this charset produce", scriptsSP, true));
add(scriptsSP);
}
private GLabel newLabel(String text, String tooltip, JComponent comp, boolean top) {
GLabel label = new GLabel(text);
if (top) {
label.setVerticalAlignment(SwingConstants.TOP);
}
label.setToolTipText(tooltip);
label.setLabelFor(comp);
return label;
}
@Override
public void setFont(Font font) {
super.setFont(font);
if (scriptExampleStrings != null) {
scriptExampleStrings.clear();
}
}
public void setCharset(CharsetInfo csi) {
nameTF.setText(csi.getName());
nameTF.setCaretPosition(0);
commentTA.setText(csi.getComment());
commentTA.setCaretPosition(0);
fixedCB.setSelected(csi.hasFixedLengthChars());
String min =
csi.getMinBytesPerChar() > 0 ? Integer.toString(csi.getMinBytesPerChar()) : "unknown";
String max =
csi.getMaxBytesPerChar() > 0 ? Integer.toString(csi.getMaxBytesPerChar()) : "unknown";
minmaxTF.setText("%s / %s".formatted(min, max));
minmaxTF.setCaretPosition(0);
alignTF.setText("%d".formatted(csi.getAlignment()));
alignTF.setCaretPosition(0);
scriptsList.setListData(List.copyOf(csi.getScripts()).toArray(UnicodeScript[]::new));
}
private String getScriptCellRendererText(UnicodeScript script) {
buildScriptExamplesMap(getFont());
if (script == null) {
return "";
}
String name = script.name();
String example = scriptExampleStrings.getOrDefault(script, "");
if (!example.isEmpty()) {
example = " \u2014 " + example;
}
return name + example;
}
private void buildScriptExamplesMap(Font f) {
if (scriptExampleStrings.isEmpty()) {
scriptExampleStrings.putAll(CharacterScriptUtils.getDisplayableScriptExamples(f, 7));
}
}
}

View File

@@ -0,0 +1,78 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.util.charset.picker;
import docking.DialogComponentProvider;
import docking.DockingWindowManager;
import ghidra.util.HelpLocation;
import ghidra.util.charset.CharsetInfo;
/**
* Dialog that displays a charset picker table and lets the user press ok or cancel.
* <p>
* Call {@link #getSelectedCharset()} after the dialog closes to get the selected value.
*/
public class CharsetPickerDialog extends DialogComponentProvider {
/**
* Allows user to pick a charset from a table in a modal dialog.
*
* @param defaultCSI default charset to initially select in the table
* @return selected charset, or null if canceled
*/
public static CharsetInfo pickCharset(CharsetInfo defaultCSI) {
CharsetPickerDialog dlg = new CharsetPickerDialog();
dlg.setSelectedCharset(defaultCSI);
DockingWindowManager.showDialog(dlg);
return dlg.getSelectedCharset();
}
private CharsetPickerPanel panel;
private CharsetInfo csi;
public CharsetPickerDialog() {
super("Pick Charset", true, false, true, false);
panel = new CharsetPickerPanel(null);
addWorkPanel(panel);
addOKButton();
addCancelButton();
setDefaultSize(800, 800);
setRememberLocation(false);
setHelpLocation(new HelpLocation("Charsets", "CharsetPicker"));
}
@Override
protected void okCallback() {
this.csi = panel.getSelectedCharset();
close();
}
@Override
protected void cancelCallback() {
this.csi = null;
super.cancelCallback();
}
public void setSelectedCharset(CharsetInfo csi) {
panel.setSelectedCharset(csi);
}
public CharsetInfo getSelectedCharset() {
return csi;
}
}

View File

@@ -0,0 +1,103 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.util.charset.picker;
import java.awt.BorderLayout;
import java.nio.charset.Charset;
import java.util.function.Consumer;
import javax.swing.*;
import javax.swing.table.TableColumn;
import ghidra.util.charset.CharsetInfo;
import ghidra.util.table.GhidraTable;
import ghidra.util.table.GhidraTableFilterPanel;
/**
* JPanel that displays a table of all charsets on top and a detail panel on bottom.
*/
public class CharsetPickerPanel extends JPanel {
private GhidraTable table;
private CharsetTableModel tableModel = new CharsetTableModel();
private GhidraTableFilterPanel<CharsetTableRow> tableFilterPanel;
private Consumer<Charset> charsetListener;
private CharsetInfo selectedCSI;
public CharsetPickerPanel(Consumer<Charset> charsetListener) {
super(new BorderLayout());
build();
this.charsetListener = charsetListener;
}
private void build() {
table = new GhidraTable(tableModel);
table.setVisibleRowCount(10);
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
tableFilterPanel = new GhidraTableFilterPanel<>(table, tableModel);
JScrollPane scrollPane = new JScrollPane(table);
CharsetInfoPanel detailsPanel = new CharsetInfoPanel();
detailsPanel.getAccessibleContext().setAccessibleName("Details");
detailsPanel.setBorder(BorderFactory.createTitledBorder("Details"));
JPanel innerPanel = new JPanel(new BorderLayout());
innerPanel.add(scrollPane, BorderLayout.CENTER);
innerPanel.add(tableFilterPanel, BorderLayout.SOUTH);
innerPanel.getAccessibleContext().setAccessibleName("Table Filter");
add(innerPanel, BorderLayout.CENTER);
add(detailsPanel, BorderLayout.SOUTH);
table.getSelectionModel().addListSelectionListener(e -> {
if (!e.getValueIsAdjusting()) {
CharsetTableRow row = tableFilterPanel.getSelectedItem();
if (row != null) {
selectedCSI = row.csi();
detailsPanel.setCharset(row.csi());
if (charsetListener != null) {
charsetListener.accept(row.csi().getCharset());
}
}
}
});
TableColumn col = table.getColumnModel().getColumn(CharsetTableModel.MINLEN_COL);
col.setMaxWidth(100);
col = table.getColumnModel().getColumn(CharsetTableModel.MAXLEN_COL);
col.setMaxWidth(100);
col = table.getColumnModel().getColumn(CharsetTableModel.FIXEDLEN_COL);
col.setMaxWidth(100);
}
public void setSelectedCharset(CharsetInfo csi) {
int rowNum = tableModel.findCharset(csi);
if (rowNum >= 0) {
table.getSelectionManager().setSelectionInterval(rowNum, rowNum);
table.scrollToSelectedRow();
}
}
public void setCharsetListener(Consumer<Charset> charsetListener) {
this.charsetListener = charsetListener;
}
public CharsetInfo getSelectedCharset() {
return selectedCSI;
}
}

View File

@@ -0,0 +1,125 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.util.charset.picker;
import java.lang.Character.UnicodeScript;
import java.util.*;
import docking.widgets.table.AbstractSortedTableModel;
import ghidra.util.charset.CharsetInfo;
import ghidra.util.charset.CharsetInfoManager;
class CharsetTableModel extends AbstractSortedTableModel<CharsetTableRow> {
final static int NAME_COL = 0;
final static int COMMENT_COL = 1;
final static int FIXEDLEN_COL = 2;
final static int MINLEN_COL = 3;
final static int MAXLEN_COL = 4;
final static int SCRIPTS_COL = 5;
final static String[] COL_NAMES =
new String[] { "Name", "Description", "Fixed Length", "Min BPC", "Max BPC", "Scripts" };
private List<CharsetTableRow> charsets = new ArrayList<>();
public CharsetTableModel() {
CharsetInfoManager.getInstance()
.getCharsets()
.stream()
.map(csi -> new CharsetTableRow(csi, getScriptsString(csi.getScripts())))
.forEach(charsets::add);
}
private static String getScriptsString(Set<UnicodeScript> scripts) {
StringBuilder sb = new StringBuilder();
for (UnicodeScript script : scripts) {
if (!sb.isEmpty()) {
sb.append(", ");
}
sb.append(script.name());
}
return sb.toString();
}
public int findCharset(CharsetInfo csi) {
for (int i = 0; i < charsets.size(); i++) {
CharsetTableRow row = charsets.get(i);
if (row.csi().getName().equals(csi.getName())) {
return i;
}
}
return -1;
}
@Override
public String getName() {
return "Charsets";
}
@Override
public int getColumnCount() {
return COL_NAMES.length;
}
@Override
public String getColumnName(int column) {
return 0 <= column && column < COL_NAMES.length ? COL_NAMES[column] : "<<unknown>>";
}
@Override
public Class<?> getColumnClass(int columnIndex) {
switch (columnIndex) {
case NAME_COL:
return String.class;
case COMMENT_COL:
return String.class;
case FIXEDLEN_COL:
return Boolean.class;
case MINLEN_COL:
return Integer.class;
case MAXLEN_COL:
return Integer.class;
case SCRIPTS_COL:
return String.class;
}
return Object.class;
}
@Override
public boolean isSortable(int columnIndex) {
return true;
}
@Override
public List<CharsetTableRow> getModelData() {
return charsets;
}
@Override
public Object getColumnValueForRow(CharsetTableRow row, int column) {
return switch (column) {
case NAME_COL -> row.csi().getName();
case COMMENT_COL -> row.csi().getComment();
case FIXEDLEN_COL -> row.csi().hasFixedLengthChars();
case MINLEN_COL -> row.csi().getMinBytesPerChar();
case MAXLEN_COL -> row.csi().getMaxBytesPerChar();
case SCRIPTS_COL -> row.scripts();
default -> "???";
};
}
}

View File

@@ -0,0 +1,20 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.util.charset.picker;
import ghidra.util.charset.CharsetInfo;
record CharsetTableRow(CharsetInfo csi, String scripts) {}

View File

@@ -7,213 +7,214 @@
</head>
<body lang="EN-US">
<h1><a name="ByteViewerPlugin"></a><a name="Bytes"></a>The Byte Viewer </h1>
<h1><a name="ByteViewerPlugin"></a><a name="Bytes"></a>The Byte Viewer</h1>
<p>The Byte Viewer displays bytes in memory in various formats, e.g.,
Hex, Ascii, Octal, etc.&nbsp;The figure below shows the Byte Viewer
Hex, Characters, Octal, etc. The figure below shows the Byte Viewer
plugin in a separate window from the
<a href="help/topics/Tool/Ghidra_Tool_Administration.htm#DefaultTools">default
tool</a>, the Code Browser.&nbsp;</p>
tool</a>, the Code Browser.</p>
<p align="center"><img alt="" src="images/ByteViewer.png" ><br> &nbsp;</p>
<p align="center"><img alt="" src="images/ByteViewer.png" ><br></p>
<p>To show the Byte Viewer, select the icon, <img
src="images/binaryData.gif" border="0">,
on the Code Browser toolbar, OR, choose the <b>Window</b><img
src="help/shared/arrow.gif" border="0" ><b>Bytes: ...</b> menu.</p>
<p> The following paragraphs describe the Byte Viewer.</p>
<p>To show the Byte Viewer, select the icon, <img src="images/binaryData.gif" border="0">,
on the Code Browser toolbar, OR, choose the <b>Window</b>
<img src="help/shared/arrow.gif" border="0"> <b>Bytes: ...</b> menu.</p>
<p>The following paragraphs describe the Byte Viewer.</p>
<h2><a name="ToggleFormatAction"></a><a name="formats"></a>Data Formats</h2>
<blockquote>
<p>This section describes the formats that Ghidra provides by
default.&nbsp; Each format is an instance of a DataFormatModel interface,
so any <a href="#WriteYourOwn">
new formats that you provide</a> will automatically show up in the <span
style="font-style: italic;">Byte Viewer Options </span>dialog that
lists the data formats that
may be added to your view.&nbsp;To add or remove a data format view
from the tool, press the&nbsp;<img alt="" src="images/wrench.png">
icon to bring up the&nbsp; <span
style="font-style: italic;">Byte Viewer Options </span>dialog.&nbsp;
Select the formats that you want and press the <span
style="font-weight: bold;">OK </span>button.<br>
</p>
default. Each format is an instance of a DataFormatModel interface,
so any <a href="#WriteYourOwn">new formats that you provide</a>
will automatically show up in the <i>Byte Viewer Options</i> dialog that
lists the data formats that may be added to your view.</p>
<p>To add or remove a data format view from the tool, press the
<img alt="" src="images/wrench.png"> icon to bring up the
<i>Byte Viewer Options</i> dialog. Select the formats that you want and press the
<b>OK</b> button.</p>
</blockquote>
<h3><a name="Hex"></a><a name="Add_Byteviewer_Hex_Panel"></a>Hex </h3>
<blockquote>
<p>The Hex view shows each byte as a two character hex value. <a
<h3><a name="Hex"></a><a name="Add_Byteviewer_Hex_Panel"></a>Hex</h3>
<blockquote>
<p>The <b>Hex</b> view shows each byte as a two character hex value. <a
href="#GroupSize">Change the group size</a> for the Hex format to show
the bytes grouped in that size.&nbsp;When you add the Byte Viewer
the bytes grouped in that size. When you add the Byte Viewer
plugin to a tool and then open a program, the Hex view is automatically
displayed by default.&nbsp;</p>
displayed by default.</p>
<p> This view supports byte <a href="#EditBytes">editing</a>.&nbsp;</p>
</blockquote>
<h3><a name="Ascii"></a><a name="Add_Byteviewer_Ascii_Panel"></a>Ascii </h3>
<blockquote>
<p>The Ascii view shows each byte as its equivalent Ascii character.
For those bytes that do not represent an Ascii character, the format shows
<p>This view supports byte <a href="#EditBytes">editing</a>.</p>
</blockquote>
<h3><a name="Chars"></a><a name="Add_Byteviewer_Chars_Panel"></a>Chars</h3>
<blockquote>
<p>The <b>Chars</b> view shows each byte (or group of bytes) as its equivalent
character, using a JVM installed charset. Typical examples will be US-ASCII
(1 byte per character), UTF-8 (between 1 and 3 bytes per character), UTF-16
(typically 2 bytes per character, but can also require 4 bytes for some).</p>
<p>For those bytes that do not encode a valid Unicode character, the view shows
it as a tic (".").</p>
<p> This view supports byte <a href="#EditBytes">editing</a>.&nbsp;</p>
</blockquote>
<h3><a name="Add_Byteviewer_Address_Panel"></a><a name="Address"></a>Address</h3>
<blockquote>
<p> The Address view displays&nbsp; a tic (".") for all bytes whose
<p>Some characters, though valid, will not be able to be displayed if the active
font does not support that character. Typically these characters are rendered
as a blank square or some other distinctive shape to indicate the issue.</p>
<p>This view supports byte <a href="#EditBytes">editing</a>.</p>
</blockquote>
<h3><a name="Add_Byteviewer_Address_Panel"></a><a name="Address"></a>Address</h3>
<blockquote>
<p>The <b>Address</b> view displays a tic (".") for all bytes whose
formed address does not fall within the range of memory for the
program. For those addresses that can be formed and are in memory, the
view shows the symbol, <img src="images/addressMark.png" border="0">
&nbsp; So if you go to that address in the <a
href="help/topics/CodeBrowserPlugin/CodeBrowser.htm"> Code Browser</a>, and
So if you go to that address in the <a
href="help/topics/CodeBrowserPlugin/CodeBrowser.htm">Code Browser</a>, and
<a href="help/topics/DataPlugin/Data.htm#Pointer">make a
Pointer data type</a>, the address pointed to is in memory. Conversely, if
you go to a "tic" address in the Code Browser and make a pointer, the
address pointed to is not in memory (the operand is
rendered in red).</font></p>
<p>This view does not support <a href="#EditBytes">editing</a>.</p>
</blockquote>
<h3><a name="Add_Byteviewer_Disassembled_Panel"></a><a name="Disassembled"></a>Disassembled</h3>
<blockquote>
<p> The Disassemble view shows a "box" (<img src="images/box.gif"
border="0" > ) symbol for each address that has
undefined bytes. For those addresses that are <a
href="help/topics/Glossary/glossary.htm#Instruction"> instructions</a>
or <a href="help/topics/Glossary/glossary.htm#DataItem"> defined data</a>, the
view
shows a tic ("."). With this view, you can easily see what areas of the
address pointed to is not in memory (the operand is rendered in red).</p>
<p>This view does not support <a href="#EditBytes">editing</a>.</p>
</blockquote>
<h3><a name="Add_Byteviewer_Disassembled_Panel"></a><a name="Disassembled"></a>Disassembled</h3>
<blockquote>
<p>The <b>Disassemble</b> view shows a "box"
(<img src="images/box.gif" border="0" >) symbol
for each address that has undefined bytes. For those addresses that are
<a href="help/topics/Glossary/glossary.htm#Instruction">instructions</a> or
<a href="help/topics/Glossary/glossary.htm#DataItem">defined data</a>, the
view shows a tic ("."). With this view, you can easily see what areas of the
program have been disassembled.</p>
<p>This view does not support <a href="#EditBytes">editing</a>.</p>
</blockquote>
<h3><a name="Add_Byteviewer_HexShortPanel"></a><a name="HexShort"></a>Hex Short</h3>
<blockquote>
<p>This format shows two-byte numbers represented as an four-digit hex number.&nbsp;</p>
<p> This view supports <a href="#EditBytes">editing</a>. When a byte
is changed, both bytes associated with this address are rendered in
<font color="#ff0000"> red</font> to denote the change.</p>
</blockquote>
<p>This view does not support <a href="#EditBytes">editing</a>.</p>
</blockquote>
<h3><a name="Add_Byteviewer_HexInteger_Panel"></a><a name="HexInteger"></a>Hex Integer</h3>
<blockquote>
<p>This format shows four-byte numbers represented as an eight-digit hex number.&nbsp;</p>
<p> This view supports <a href="#EditBytes">editing</a>. When a byte
<h3><a name="Add_Byteviewer_HexShortPanel"></a><a name="HexShort"></a>Hex Short</h3>
<blockquote>
<p>This format shows two-byte numbers represented as an four-digit hex number.</p>
<p>This view supports <a href="#EditBytes">editing</a>. When a byte is changed,
both bytes associated with this address are rendered in
<font color="#ff0000">red</font> to denote the change.</p>
</blockquote>
<h3><a name="Add_Byteviewer_HexInteger_Panel"></a><a name="HexInteger"></a>Hex Integer</h3>
<blockquote>
<p>This format shows four-byte numbers represented as an eight-digit hex number.</p>
<p>This view supports <a href="#EditBytes">editing</a>. When a byte
is changed, all four bytes associated with this address are rendered in
<font color="#ff0000"> red</font> to denote the change.</p>
</blockquote>
<font color="#ff0000">red</font> to denote the change.</p>
</blockquote>
<h3><a name="Add_Byteviewer_HexLong_Panel"></a><a name="HexLong"></a>Hex Long</h3>
<blockquote>
<p>This format shows eight-byte numbers represented as an 16-digit hex number.&nbsp;</p>
<p> This view supports <a href="#EditBytes">editing</a>. When a byte
<h3><a name="Add_Byteviewer_HexLong_Panel"></a><a name="HexLong"></a>Hex Long</h3>
<blockquote>
<p>This format shows eight-byte numbers represented as an 16-digit hex number.</p>
<p>This view supports <a href="#EditBytes">editing</a>. When a byte
is changed, all eight bytes associated with this address are rendered in
<font color="#ff0000"> red</font> to denote the change.</p>
</blockquote>
<font color="#ff0000">red</font> to denote the change.</p>
</blockquote>
<h3><a name="Add_Byteviewer_HexLongLong_Panel"></a><a name="HexLongLong"></a>Hex Long Long</h3>
<blockquote>
<p>This format shows 16-byte numbers represented as an 32-digit hex number.&nbsp;</p>
<p> This view supports <a href="#EditBytes">editing</a>. When a byte
<h3><a name="Add_Byteviewer_HexLongLong_Panel"></a><a name="HexLongLong"></a>Hex Long Long</h3>
<blockquote>
<p>This format shows 16-byte numbers represented as an 32-digit hex number.</p>
<p>This view supports <a href="#EditBytes">editing</a>. When a byte
is changed, all 16 bytes associated with this address are rendered in
<font color="#ff0000"> red</font> to denote the change.</p>
</blockquote>
<font color="#ff0000">red</font> to denote the change.</p>
</blockquote>
<h3><a name="Add_Byteviewer_Integer_Panel"></a><a name="Integer"></a>Integer&nbsp; </h3>
<blockquote>
<p>This view shows four-byte numbers represented in decimal format.&nbsp;</p>
<p> This view does not support <a href="#EditBytes">editing</a>.</p>
</blockquote>
<h3><a name="Octal"></a><a name="Add_Byteviewer_Octal_Panel"></a>Octal </h3>
<blockquote>
<p>The octal view shows each byte as a three character octal value.&nbsp;</p>
<p> This view supports <a href="The_Byte_Viewer.htm#EditBytes">editing</a>.</p>
</blockquote>
<h3><a name="Binary"></a><a name="Add_Byteviewer_Binary_Panel"></a>Binary </h3>
<blockquote>
<p>The binary view shows each byte as an eight character binary value.&nbsp;</p>
<p> This view supports <a href="#EditBytes">editing</a>.</p>
</blockquote>
<h3><a name="Add_Byteviewer_Integer_Panel"></a><a name="Integer"></a>Integer</h3>
<blockquote>
<p>This view shows four-byte numbers represented in decimal format.</p>
<p>This view does not support <a href="#EditBytes">editing</a>.</p>
</blockquote>
<h3><a name="Octal"></a><a name="Add_Byteviewer_Octal_Panel"></a>Octal </h3>
<blockquote>
<p>The octal view shows each byte as a three character octal value.</p>
<p>This view supports <a href="The_Byte_Viewer.htm#EditBytes">editing</a>.</p>
</blockquote>
<h3><a name="Binary"></a><a name="Add_Byteviewer_Binary_Panel"></a>Binary </h3>
<blockquote>
<p>The binary view shows each byte as an eight character binary value.</p>
<p>This view supports <a href="#EditBytes">editing</a>.</p>
</blockquote>
</blockquote>
<h2>Status Fields</h2>
<blockquote>
<p>The&nbsp; labels below the scroll pane that contains the views shows the following information:</p>
<p>&nbsp;</p>
<div align="left">
<table border="1" width="65%">
<tbody>
<tr>
<td width="20%">Start</td>
<td width="80%">The minimum address of Memory</td>
</tr>
<tr>
<td width="20%">End</td>
<td width="80%">The maximum address of Memory</td>
</tr>
<tr>
<td width="20%"><a name="OffsetField"></a>Offset</td>
<td width="80%">Displayed in decimal, the
<p>The labels below the scroll pane that contains the views shows the following
information:</p>
<div align="left">
<table border="1" width="65%">
<tbody>
<tr>
<td width="20%">Start</td>
<td width="80%">The minimum address of Memory</td>
</tr>
<tr>
<td width="20%">End</td>
<td width="80%">The maximum address of Memory</td>
</tr>
<tr>
<td width="20%"><a name="OffsetField"></a>Offset</td>
<td width="80%">Displayed in decimal, the
number of bytes added to each block of memory that is being displayed.
This number is calculated when you set the <a href="#AlignmentAddress">alignment
address</a> or the number of bytes per line.&nbsp;</td>
</tr>
<tr>
<td width="20%">Insertion</td>
<td width="80%">The address of your current cursor location</td>
</tr>
</tbody>
</table>
</div>
This number is calculated when you set the <a href="#AlignmentOffset">alignment
offset</a> or the number of bytes per line.</td>
</tr>
<tr>
<td width="20%">Insertion</td>
<td width="80%">The address of your current cursor location</td>
</tr>
</tbody>
</table>
</div>
</blockquote>
<p>&nbsp;</p>
<h2><a name="Enable_Disable_Byteviewer_Editing"></a><a name="EditBytes"></a>Editing Memory </h2>
<h2><a name="Enable_Disable_Byteviewer_Editing"></a><a name="EditBytes"></a>Editing Memory</h2>
<blockquote>
<p>To enable byte editing,&nbsp;</p>
<ol>
<li>Toggle the Enable/Disable Edit toolbar button <img
src="images/editbytes.gif" alt="" >
&nbsp;so that it appears pushed-in.&nbsp;</li>
<li> Click in a view that supports editing, e.g., Hex or Ascii&nbsp;</li>
<li> The cursor changes to red to indicate that this view can be edited.&nbsp;</li>
</ol>
<p> Changing bytes is allowed only if your cursor is at an address
that does not contain an instruction. If you attempt to change a byte
of an instruction, an "editing not allowed" message is displayed in the
status area of the
tool.&nbsp;</p>
<p>To enable byte editing,</p>
<ol>
<li>Toggle the Enable/Disable Edit toolbar button
<img src="images/editbytes.gif" alt="" > so that it appears pushed-in.</li>
<li>Click in a view that supports editing, e.g., Hex or Chars</li>
<li>The cursor changes to red to indicate that this view can be edited.</li>
</ol>
<p>Changing bytes is allowed only if your cursor is at an address
that does not contain an instruction. If you attempt to change a byte
of an instruction, an "editing not allowed" message is displayed in the
status area of the tool.</p>
</blockquote>
<blockquote>
<p>Changed bytes are rendered in <font color="#ff0000">red</font>.
This color can be changed via the <font color="#000000"><a
href="ByteViewerOptions.htm#EditColorsAndFont">Byte Viewer Edit Options</a></font><font
color="#0000ff"> </font><font color="#000000">by double-clicking on
the <i><a href="ByteViewerOptions.htm#EditColor">Edit Color</a></i>
field</font>.</p>
<p>Changed bytes are rendered in <font color="#ff0000">red</font>.
This color can be changed via the
<a href="ByteViewerOptions.htm#EditColorsAndFont">Byte Viewer Edit Options</a>
by double-clicking on the <i><a href="ByteViewerOptions.htm#EditColor">Edit Color</a></i>
field.</p>
<p><font color="#000000">Undo the edit by hitting the Undo button (<img
src="icon.undo" border="0" > ) on
<p>Undo the edit by hitting the Undo button (<img src="icon.undo" border="0" >) on
the tool. The byte reverts to its original value. Redo your edit by
hitting the Redo button (<img src="icon.redo" border="0"> ).</font></p>
hitting the Redo button (<img src="icon.redo" border="0">).</p>
</blockquote>
<blockquote>
<p><font color="#000000">To turn off byte editing, click the
Enable/Disable
Edit toolbar button </font><img src="images/editbytes.gif" alt="">
so that it no longer appears pushed-in<font
color="#000000">.</font></p>
<p><img src="help/shared/note.png" border="0">If
you have two Byte Viewers running, you can <a
href="help/topics/FrontEndPlugin/Connecting_Tools.htm#SpecialToolEvent">connect</a>
<p>To turn off byte editing, click the Enable/Disable Edit toolbar button
<img src="images/editbytes.gif" alt=""> so that it no longer appears pushed-in.</p>
<p><img src="help/shared/note.png" border="0">If you have two Byte Viewers running, you can
<a href="help/topics/FrontEndPlugin/Connecting_Tools.htm#SpecialToolEvent">connect</a>
the two tools for the "Byte Block Edit" event so that when you make
changes
in one Byte Viewer, the other will reflect those changes in red.</p>
changes in one Byte Viewer, the other will reflect those changes in red.</p>
</blockquote>
<h2>Cursor Colors</h2>
@@ -222,124 +223,172 @@
color="#000000"><B>black</B></font>. (Cursor colors can be changed via the <a
href="ByteViewerOptions.htm#EditColorsAndFont">Options</a> dialog) If
the byte editing is enabled and the view that is in focus supports
editing,
then the cursor is <font color="#ff0000"><B>red</B></font>.&nbsp;</p>
editing, then the cursor is <font color="#ff0000"><b>red</b></font>.</p>
</blockquote>
<br>
<h2><a name="Byte_Viewer_Options"></a>Byte Viewer Options:</h2>
<p style="margin-left: 40px;">The <span style="font-style: italic;">Byte
Viewer Options </span>dialog can be used to add and remove views, set the
<span style="font-style: italic;">Alignment Address</span>, set the
number of <span style="font-style: italic;">bytes per line</span>,
and set the <span style="font-style: italic;">group size</span> to be
used by the <span style="font-style: italic;">hex</span> view.&nbsp;
To launch the <span style="font-style: italic;"><span
style="font-style: italic;">Byte Viewer Options</span></span> dialog,
press the <img alt="" src="images/wrench.png"> icon on the Byte Viewer toolbar.</p>
<h2><a name="Byte_Viewer_Options"></a>Byte Viewer Options</h2>
<blockquote>
<p>The <i>Byte Viewer Options</i> dialog can be
used to add and remove views, set the <i>alignment offset</i>, set the number
of <i>bytes per line</i>, and set the <i>group size</i> to be used by the <i>hex</i>
view.</p>
<p>To launch the <i>Byte Viewer Options</i> dialog,
press the <img alt="" src="images/wrench.png"> icon on the Byte Viewer toolbar.</p>
</blockquote>
<div style="text-align: center;"><img alt=""
src="images/ByteViewerOptionsDialog.png"><br>
<div style="text-align: center;"><img alt="" src="images/ByteViewerOptionsDialog.png"><br>
</div>
<blockquote>
<h3><a name="AlignmentAddress"></a>Alignment Address&nbsp;&nbsp;</h3>
<blockquote>
<p>The alignment address specifies what address should appear in
column 0.&nbsp; Any address can be specified, but the address will be
normalized to be near the program's minimum address. This enables you to
view bytes in an offcut manner and to identify patterns in the bytes.
Changing the alignment address affects the <a href="#OffsetField">offset</a>,
which is the column that would display the bytes for address 0 if it
existed.
The offset is affected by both the alignment address and the bytes per
line. The offset is displayed as a label below the scroll
pane containing the views.</p>
<p><img src="help/shared/tip.png" border="0" >Sometimes
you might see a byte pattern such that you want all the bytes
to line up in the first column of the display. Consider the cursor
position in the image below. If you want to see the fourth column of
bytes (values of 00) to appear in the first column, you would enter an
alignment address of 0040b003, as indicated by your cursor position.</p>
<p align="center"><img src="images/ByteViewerExample.png" border="0"></p>
<p align="left">The result of setting the alignment address to 0040b003
is shown below. The calculated offset is 13, the number of bytes added
to each memory block to create a new alignment. The first line of the
display shows the "remainder" bytes of 16 (bytes per line) divided by
13, the offset. If you were to put your cursor on the starting byte of
the first line, you would see that your insertion point is 0040b000, in
this example.</p>
<p align="center"><img src="images/ByteViewerResults.png" border="0" ></p>
</blockquote>
<h3>Set Bytes Per Line <br></h3>
<blockquote>
<p>The bytes per line indicates how many bytes are displayed in one
line in a view. The default value is 16. <br></p>
<p><img src="help/shared/note.png" border="0">
All formats shown must be able to support the new value.
For example,&nbsp; since the HexInteger and Integer&nbsp; formats show
<h3>Bytes Per Line</h3>
<blockquote>
<p>The bytes per line value indicates how many bytes are displayed in one
line in a view. The default value is 16.</p>
<p><img src="help/shared/note.png" border="0">
All formats shown must be able to support the new value.
For example, since the HexInteger and Integer formats show
bytes in groups of four, the bytes per line must be a multiple of four.
If a selected format cannot support a value for the bytes per line, an
error message will appear and the <span style="font-weight: bold;">OK</span>
button will be disabled.<br></p>
</blockquote>
<h3><a name="GroupSize"></a>Set Group Size</h3>
<blockquote>
<p>The group size is the number of bytes that the Hex view shows as
error message will appear and the <b>OK</b> button will be disabled.</p>
</blockquote>
<h3><a name="AlignmentOffset"></a><a name="Shift_Alignment_Offset_Right"></a>
<a name="Shift_Alignment_Offset_Left"></a>Alignment Offset</h3>
<blockquote>
<p>The alignment offset controls which bytes should appear in column 0. This enables
you to view bytes in an offcut manner and to identify patterns in the bytes.
The offset is displayed as a label below the scroll pane containing the views.</p>
<p><img src="help/shared/tip.png" border="0">Sometimes
you might see a byte pattern such that you want all the bytes
to line up in the first column of the display. Consider the cursor position in the
image below:</p>
<p align="center"><img src="images/ByteViewerExample.png" border="0"></p>
<p>If you want the fourth column of bytes (values of 00) to appear in
the first column, you need to decrease the alignment offset by 3, either by using
the <b>Shift Bytes Left</b> popup action (or hitting <b>Ctrl+Comma</b>) a few times,
or by changing the alignment offset value in the configuration dialog.</p>
<p align="left">The result of setting the alignment offset is shown below.</p>
<p align="center"><img src="images/ByteViewerResults.png" border="0" ></p>
</blockquote>
<h3><a name="GroupSize"></a>Hex Group Size</h3>
<blockquote>
<p>The group size is the number of bytes that the Hex view shows as
a "unit." For example, a group size of two means to show two bytes
grouped together with no spaces. <br></p>
</blockquote>
<h3>View Selection<br></h3>
<blockquote>
<p>Each potential view is listed as a checkbox.&nbsp; Select the
checkboxes corresponding to the views to be shown.&nbsp; Red text
grouped together with no spaces.</p>
</blockquote>
<h3><a name="Charset"></a>Charset</h3>
<blockquote>
<p>This specifies how bytes are converted into characters. Typically
<b>US-ASCII</b> or <b>UTF-8</b> are good choices, but other charsets
that are installed in your Java JVM will be available also.</p>
<p>Character encoding schemes that use more than 1 byte to encode a
character are supported, but can produce incorrect values interlaced
with the actual text characters that should be there, as each byte is treated
as the starting position of a character, even if it was already 'used' by
a previous multi-byte character.</p>
<p>For instance, if a sequence of UTF-8 characters are being displayed, and
some characters take 1 byte while some take 2 or 3, the positions in the
<b>Chars</b> display grid that are not valid starting locations may display
garbage, or just a "." dot indicating the byte could not be decoded.</p>
<p>Character encoding schemes that rely on escape sequences to shift between
code pages will not produce useful values in the <b>Chars</b> display grid as
each byte (and some number of following bytes) are treated as a unique string
to decode and will not include any previous escape sequences that would be needed
to correctly decode the bytes in question.</p>
<p>The available charsets are listed in a dialog that is displayed
when clicking the <b>"..."</b> browse button. You can filter by name, sizes,
supported scripts, etc by using the standard table filter mechanisms.</p>
</blockquote>
<h3><a name="CompactCharWidth"></a>Compact Char Width</h3>
<blockquote>
<p>This option toggles between wide and narrow display of characters. If
displaying ASCII or other Latin alphabet based text, the narrow option should
be sufficient to be able to see each character.</p>
<p>If displaying data that has non-Latin alphabets (scripts) the wide option
will prevent characters from overwriting their neighbors.</p>
</blockquote>
<h3><a name="WideCharAlignment"></a>Use Char Alignment</h3>
<blockquote>
<p>This option will only display characters for fixed-width multi-byte character
sets (such as UTF-16, UTF-32) starting at aligned offsets, avoiding converting
bytes sequences that are not aligned with the charset alignment size.</p>
<p>Use the <a href="#AlignmentOffset">alignment offset</a> option to control
where the starting offset for aligned characters is located.</p>
<p><b>NOTE</b>: UTF-16 is not technically a fixed-width character encoding, as a
single character may need 2 or 4 bytes to be encoded. However, it is
self-synchronizing and the 2nd half of a 4 byte sequence should be ignored and
rendered as a "." dot.</p>
<p><b>NOTE</b>: An align-able charset needs to be marked with its alignment size in
the <b>charset_info.json</b> configuration file, and by default only UTF-16 and
UTF-32 have these values.</p>
</blockquote>
<h3>View Selection</h3>
<blockquote>
<p>Each potential view is listed as a checkbox. Select the
checkboxes corresponding to the views to be shown. Red text
indicates a view cannot be displayed since it doesn't support the
specified number of bytes per line.</p>
</blockquote>
</blockquote>
</blockquote>
<h2>Reorder / Resize Views</h2>
<blockquote>
<p>The various views in the ByteViewer can be reordered by dragging
the view header to the left or right of its current position. The view
positions are swapped.<br></p>
positions are swapped.</p>
<p>The width of each view can also be changed by dragging the separator
bars in the view header to the left or right. This will resize the view that is to
the left of the separator bar.<P>
the left of the separator bar.</p>
</blockquote>
<h2><a name="WriteYourOwn"></a>Writing Your Own Format Plugin</h2>
<blockquote>
<p>To supply your own format to be added to the list of views
<p>To supply your own format to be added to the list of views
displayed in the Byte Viewer,</p>
<ol>
<li>Write an implementation of the <font face="Courier New"
size="3">ghidra.app.plugin.core.format.DataFormatModel </font>interface,
which determines the format of how the bytes should be
represented.&nbsp;</li>
<li>Edit your&nbsp;<a href="help/topics/FrontEndPlugin/Edit_Plugin_Path.htm"> Plugin
<li>Write an implementation of the <font face="Courier New"
size="3">ghidra.app.plugin.core.format.DataFormatModel</font> interface,
which determines the format of how the bytes should be represented.</li>
<li>Edit your <a href="help/topics/FrontEndPlugin/Edit_Plugin_Path.htm">Plugin
path</a> to include your class files if you are running Ghidra in production
mode versus development mode; in development mode, you will have
to add your class files to your classpath in your development
environment.</li>
<li>Restart Ghidra. </li>
</ol>
to add your class files to your classpath in your development environment.</li>
<li>Restart Ghidra.</li>
</ol>
</blockquote>
<p> </p>
<p class="providedbyplugin">Provided by: <i> Byte Viewer Plugin</i></p>
<p class="relatedtopic">Related Topics: </p>
<p class="providedbyplugin">Provided by: <i>Byte Viewer Plugin</i></p>
<p class="relatedtopic">Related Topics:</p>
<ul>
<li> <a href="ByteViewerOptions.htm">Byte Viewer Options</a></li>
<li> <font color="#000000"><a href="help/topics/DataPlugin/Data.htm#Pointer">Pointer
data types</a></font></li>
<li> <a href="help/topics/CodeBrowserPlugin/CodeBrowser.htm">Code Browser</a></li>
<li> <a href="help/topics/Tool/Configure_Tool.htm">Configure Tool</a></li>
<li> <a href="help/topics/SelectBlockPlugin/Select_Block_Help.html">Select Bytes</a></li>
<li><a href="ByteViewerOptions.htm">Byte Viewer Options</a></li>
<li><a href="help/topics/DataPlugin/Data.htm#Pointer">Pointer data types</a></li>
<li><a href="help/topics/Charsets/Charsets.htm">Charsets</a></li>
<li><a href="help/topics/CodeBrowserPlugin/CodeBrowser.htm">Code Browser</a></li>
<li><a href="help/topics/Tool/Configure_Tool.htm">Configure Tool</a></li>
<li><a href="help/topics/SelectBlockPlugin/Select_Block_Help.html">Select Bytes</a></li>
</ul>
<p></p>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

@@ -45,19 +45,20 @@ public class ByteField extends SimpleTextField {
* @param startX the starting horizontal position of the field.
* @param startY the starting vertical position of the field.
* @param width the width of the field.
* @param charWidth width of a single character (and the cursor that will cover the character)
* @param allowCursorAtEnd if true, the cursor will be allowed at the end of the field.
* @param fieldOffset the column position of the fieldFactory that generated this field.
* @param index the field's index
* @param hlFactory the factory used to create highlights
*/
public ByteField(String text, FontMetrics fontMetrics, int startX, int width,
public ByteField(String text, FontMetrics fontMetrics, int startX, int width, int charWidth,
boolean allowCursorAtEnd, int fieldOffset, BigInteger index,
FieldHighlightFactory hlFactory) {
super(text, fontMetrics, startX, width, allowCursorAtEnd, hlFactory);
this.fieldOffset = fieldOffset;
this.index = index;
this.cursorWidth = fontMetrics.charWidth('W');
this.cursorWidth = charWidth;
}
@Override
@@ -89,6 +90,7 @@ public class ByteField extends SimpleTextField {
g.setColor(cursorColor);
// we don't use getCursorBounds() so that we can specify a fixed, full-width cursor
int x = startX + metrics.stringWidth(text.substring(0, cursorLoc.col()));
g.fillRect(x, -heightAbove, cursorWidth, heightAbove + heightBelow);

View File

@@ -22,8 +22,25 @@ import ghidra.program.model.listing.Function;
public class ByteViewerActionContext extends NavigatableActionContext {
private ByteViewerComponent activeColumn;
public ByteViewerActionContext(ProgramByteViewerComponentProvider provider) {
this(provider, null);
}
public ByteViewerActionContext(ProgramByteViewerComponentProvider provider,
ByteViewerComponent activeColumn) {
super(provider, provider);
this.activeColumn = activeColumn;
}
@Override
public ByteViewerComponentProvider getComponentProvider() {
return (ByteViewerComponentProvider) super.getComponentProvider();
}
public ByteViewerComponent getActiveColumn() {
return activeColumn;
}
@Override

View File

@@ -0,0 +1,58 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.byteviewer;
import java.awt.Color;
import java.math.BigInteger;
import java.util.function.Supplier;
import docking.widgets.fieldpanel.support.BackgroundColorModel;
/**
* {@link BackgroundColorModel} that changes the color for the currently focused byteviewer row
*/
public class ByteViewerBGColorModel implements BackgroundColorModel {
private Color bgColor = ByteViewerComponentProvider.BG_COLOR;
private Supplier<BigInteger> cursorLocSupplier;
/**
* Creates new model.
*
* @param cursorLocSupplier provides the index of the byteviewer-global cursor
*/
public ByteViewerBGColorModel(Supplier<BigInteger> cursorLocSupplier) {
this.cursorLocSupplier = cursorLocSupplier;
}
@Override
public Color getBackgroundColor(BigInteger index) {
BigInteger cursorIndex = cursorLocSupplier.get();
return cursorIndex.equals(index)
? ByteViewerComponentProvider.CURRENT_LINE_COLOR
: bgColor;
}
@Override
public Color getDefaultBackgroundColor() {
return bgColor;
}
@Override
public void setDefaultBackgroundColor(Color c) {
bgColor = c;
}
}

View File

@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -135,7 +135,7 @@ public class ByteViewerClipboardProvider extends ByteCopier
@Override
public boolean isValidContext(ActionContext context) {
return context.getComponentProvider() == provider;
return context.getComponentProvider() == provider && currentProgram != null;
}
@Override
@@ -179,7 +179,7 @@ public class ByteViewerClipboardProvider extends ByteCopier
@Override
public boolean canPaste(DataFlavor[] availableFlavors) {
if (!pasteEnabled) {
if (!pasteEnabled || currentProgram == null) {
return false;
}
if (availableFlavors != null) {

View File

@@ -15,21 +15,30 @@
*/
package ghidra.app.plugin.core.byteviewer;
import java.awt.Color;
import java.awt.FontMetrics;
import java.awt.*;
import java.awt.event.*;
import java.math.BigInteger;
import java.util.List;
import docking.DockingUtils;
import javax.swing.JComponent;
import javax.swing.JToolTip;
import docking.*;
import docking.action.DockingActionIf;
import docking.actions.PopupActionProvider;
import docking.widgets.EventTrigger;
import docking.widgets.fieldpanel.FieldPanel;
import docking.widgets.fieldpanel.Layout;
import docking.widgets.fieldpanel.field.Field;
import docking.widgets.fieldpanel.listener.*;
import docking.widgets.fieldpanel.support.*;
import generic.theme.GColor;
import generic.theme.Gui;
import ghidra.app.plugin.core.format.*;
import ghidra.app.plugin.core.hover.AbstractHoverProvider;
import ghidra.app.services.HoverService;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
import ghidra.util.Msg;
import ghidra.util.Swing;
import help.Help;
@@ -39,44 +48,32 @@ import help.HelpService;
* FieldViewer to show data formatted according to the DataFormatModel that is passed in to the
* constructor. The source of the data is an array of ByteBlocks that is managed by an IndexMap.
*/
public class ByteViewerComponent extends FieldPanel implements FieldMouseListener,
FieldLocationListener, FieldSelectionListener, FieldInputListener {
public class ByteViewerComponent extends FieldPanel
implements FieldMouseListener, FieldLocationListener, FieldSelectionListener,
FieldInputListener, PopupActionProvider, ByteViewerComponentNamer {
private ByteViewerPanel panel;
private DataFormatModel model;
private int bytesPerLine;
private FieldFactory[] fieldFactories;
private FontMetrics fm;
private int charWidth;
private IndexMap indexMap;
private ProgramByteBlockSet blockSet;
private boolean consumeKeyStrokes;
private boolean editMode; // true if this component is in edit mode; cursor is different color.
//@formatter:off
private Color editedTextColor = ByteViewerComponentProvider.EDITED_TEXT_COLOR;
private Color focusedEditCursorColor = ByteViewerComponentProvider.CURSOR_COLOR_FOCUSED_EDIT;
private Color unfocusedEditCursorColor = ByteViewerComponentProvider.CURSOR_COLOR_UNFOCUSED_EDIT;
private Color focusedNonEditCursorColor = ByteViewerComponentProvider.CURSOR_COLOR_FOCUSED_NON_EDIT;
private Color unfocusedNonEditCursorColor = ByteViewerComponentProvider.CURSOR_COLOR_UNFOCUSED_NON_EDIT;
private Color currentCursorLineColor = ByteViewerComponentProvider.CURRENT_LINE_COLOR;
//@formatter:on
private ByteViewerLayoutModel layoutModel;
private boolean doingRefresh;
private boolean doingEdit;
private boolean updatingIndexMap;
private Runnable updateColorRunner;
private boolean indexUpdate = true;
private FieldLocation lastFieldLoc;
private ByteViewerHighlighter highlightProvider = new ByteViewerHighlighter();
private int highlightButton = MouseEvent.BUTTON2;
private FieldSelectionListener liveSelectionListener = (selection, trigger) -> {
ByteBlockSelection sel = processFieldSelection(selection);
panel.updateLiveSelection(ByteViewerComponent.this, sel);
};
private ByteViewerHoverProvider byteViewerHoverProvider;
/**
* Constructor
@@ -85,25 +82,28 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene
* @param layoutModel the layout model for this component
* @param model data format model that knows how the data should be displayed
* @param bytesPerLine number of bytes displayed in a row
* @param fm the font metrics used for drawing
*/
protected ByteViewerComponent(ByteViewerPanel vpanel, ByteViewerLayoutModel layoutModel,
DataFormatModel model, int bytesPerLine, FontMetrics fm) {
DataFormatModel model, int bytesPerLine) {
super(layoutModel, "Byte Viewer");
setFieldDescriptionProvider((l, f) -> getFieldDescription(l, f));
this.panel = vpanel;
this.model = model;
this.bytesPerLine = bytesPerLine;
this.fm = fm;
this.layoutModel = layoutModel;
setName(model.getName());
getAccessibleContext().setAccessibleName("Byte Viewer " + model.getName());
initialize();
}
// specialized line coloring
setBackgroundColorModel(new ByteViewerBackgroundColorModel());
private boolean isEditMode() {
return panel.getEditMode();
}
private boolean isActiveComponent() {
return panel.getCurrentComponent() == this;
}
private String getFieldDescription(FieldLocation fieldLoc, Field field) {
@@ -129,6 +129,19 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene
return block.getLocationRepresentation(offset);
}
@Override
public String getByteViewerComponentName() {
return model.getDescriptiveName();
}
@Override
public List<DockingActionIf> getPopupActions(Tool tool, ActionContext context) {
if (model instanceof PopupActionProvider popupProvider) {
return popupProvider.getPopupActions(tool, context);
}
return null;
}
@Override
public void buttonPressed(FieldLocation fieldLocation, Field field, MouseEvent mouseEvent) {
if (fieldLocation == null || field == null) {
@@ -139,7 +152,7 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene
return;
}
if (mouseEvent.getButton() == highlightButton) {
if (mouseEvent.getButton() == panel.getHighlightButton()) {
String text = field.getText();
if (text.equals(highlightProvider.getText())) {
highlightProvider.setText(null);
@@ -154,6 +167,10 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene
mouseEvent.getButton() == MouseEvent.BUTTON1) {
fieldLocationChanged(fieldLocation, field, true, false);
}
else if (!isActiveComponent()) {
// there was a click but the view wasn't the active view
fieldLocationChanged(fieldLocation, field, false, true);
}
}
@Override
@@ -164,22 +181,30 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene
private void fieldLocationChanged(FieldLocation loc, Field field, boolean isAltDown,
boolean setCurrentView) {
// tell the panel that the location has changed
// translate location
if (doingRefresh || doingEdit || loc == null || indexMap == null || field == null ||
updatingIndexMap) {
return;
}
if (!(field instanceof ByteField) || (!isAltDown && loc.equals(lastFieldLoc))) {
return;
if (indexMap.isBlockSeparatorIndex(loc.getIndex())) {
// special handling for non-byte mapped lines to insure other columns remain in sync
panel.setCurrentNonMappedIndex(loc.getIndex(), this);
}
if (lastFieldLoc == null || !loc.getIndex().equals(lastFieldLoc.getIndex())) {
// needed because the index column doesn't have a cursor that causes it to always
// be repainted and have the ability to repaint the current line background
panel.updateIndexColumnCurrentLine();
}
if (setCurrentView) {
//Set this component as the current view in the panel
panel.setCurrentView(ByteViewerComponent.this);
panel.setCurrentView(this);
}
if (!(field instanceof ByteField) || (!isAltDown && loc.equals(lastFieldLoc))) {
return;
}
// Update later because the field panel listener is called after this one, and sets the
// colors incorrectly
Swing.runLater(updateColorRunner);
Swing.runLater(() -> updateColors());
lastFieldLoc = loc;
@@ -229,7 +254,7 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene
Field field) {
panel.setStatusMessage("");
if (!consumeKeyStrokes) {
if (!isEditMode()) {
return;
}
@@ -245,7 +270,7 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene
return;
}
if (!model.isEditable()) {
if (!(model instanceof MutableDataFormatModel mutableModel)) {
panel.setStatusMessage(model.getName() + " view is not editable");
ev.consume(); // we are in edit mode-don't let the event go through
return;
@@ -295,7 +320,7 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene
}
try {
byte[] oldValue = getByteValue(block, offset);
boolean success = model.replaceValue(block, offset, col, c);
boolean success = mutableModel.replaceValue(block, offset, col, c);
if (success) {
byte[] newValue = getByteValue(block, offset);
blockSet.notifyByteEditing(block, offset, oldValue, newValue);
@@ -315,10 +340,7 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene
getToolkit().beep();
}
catch (AddressOutOfBoundsException e) {
getToolkit().beep();
}
catch (IndexOutOfBoundsException e) {
catch (AddressOutOfBoundsException | IndexOutOfBoundsException e) {
getToolkit().beep();
}
catch (Throwable t) {
@@ -332,6 +354,23 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene
}
}
@Override
public void setFont(Font font) {
super.setFont(font);
fm = getFontMetrics(getFont());
if (model != null && layoutModel != null) {
invalidateModelFields();
}
}
void invalidateModelFields() {
charWidth = model instanceof CursorWidthDataFormatModel cwdfm
? cwdfm.getCursorWidth(fm)
: fm.charWidth('W');
createFields(); // redo the fields...
layoutModel.setIndexMap(indexMap);
}
private byte[] getByteValue(ByteBlock block, BigInteger offset) {
byte[] b = new byte[model.getUnitByteSize()];
try {
@@ -354,40 +393,6 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene
addFieldMouseListener(this);
}
void setFontMetrics(FontMetrics fm) {
this.fm = fm;
createFields();
layoutModel.setIndexMap(indexMap);
}
void setHighlightButton(int highlightButton) {
this.highlightButton = highlightButton;
}
void setMouseButtonHighlightColor(Color color) {
highlightProvider.setHighlightColor(color);
}
/**
* Set the color for showing gaps in indexes.
*
* @param c the color to set
*/
void setSeparatorColor(Color c) {
for (FieldFactory fieldFactorie : fieldFactories) {
fieldFactorie.setSeparatorColor(c);
}
layoutModel.layoutChanged();
}
/**
* Get the color of unsaved byte changes
* @return the color of unsaved byte changes
*/
Color getEditedTextColor() {
return editedTextColor;
}
void setIndexMap(IndexMap map) {
updatingIndexMap = true;
indexMap = map;
@@ -397,13 +402,9 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene
createFields();
}
ByteBlockSet byteBlockSet = indexMap.getByteBlockSet();
if (byteBlockSet instanceof ProgramByteBlockSet) {
blockSet = (ProgramByteBlockSet) indexMap.getByteBlockSet();
}
else {
blockSet = null;
}
blockSet = indexMap.getByteBlockSet() instanceof ProgramByteBlockSet pbbs ? pbbs : null;
byteViewerHoverProvider
.setProgram(blockSet != null && blockSet.isValid() ? blockSet.program : null);
if (indexUpdate) {
layoutModel.setIndexMap(indexMap);
}
@@ -418,18 +419,6 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene
return blockSet;
}
/**
* Set the new group size
*
* @param groupSize the group size
* @throws UnsupportedOperationException if model for this view does not support groups
*/
void setGroupSize(int groupSize) {
model.setGroupSize(groupSize);
createFields(); // redo the fields...
layoutModel.setIndexMap(indexMap);
}
void setViewerSelection(ByteBlockSelection selection) {
removeFieldSelectionListener(this);
try {
@@ -506,7 +495,7 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene
int fieldNum = location.getFieldNum();
int row = location.getRow();
setCursorPosition(fieldIndex, fieldNum, row, column, EventTrigger.INTERNAL_ONLY);
if (panel.getCurrentComponent() == this) {
if (isActiveComponent()) {
goTo(fieldIndex, fieldNum, row, column, false, EventTrigger.INTERNAL_ONLY);
}
@@ -599,7 +588,7 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene
* Convert the cursor location to a byte block and an offset.
* @return the cursor location to a byte block and an offset.
*/
ByteBlockInfo getViewerCursorLocation() {
public ByteBlockInfo getViewerCursorLocation() {
FieldLocation loc = getCursorLocation();
if (loc == null) {
ViewerPosition vp = getViewerPosition();
@@ -636,39 +625,16 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene
return model;
}
/**
* Set the edit mode according to the given param if the model for this view supports editing.
*
* @param editMode true means to enable editing, and change the cursor color.
*/
void setEditMode(boolean editMode) {
consumeKeyStrokes = editMode;
if (!model.isEditable()) {
return;
}
this.editMode = editMode;
updateFocusedColor();
udpateNonFocusedColor();
private Color getActiveColor() {
return isEditMode()
? ByteViewerComponentProvider.CURSOR_COLOR_FOCUSED_EDIT
: ByteViewerComponentProvider.CURSOR_COLOR_FOCUSED_NON_EDIT;
}
private void updateFocusedColor() {
if (panel.getCurrentComponent() == this) {
if (editMode) {
setFocusedCursorColor(focusedEditCursorColor);
}
else {
setFocusedCursorColor(focusedNonEditCursorColor);
}
}
}
private void udpateNonFocusedColor() {
Color c = editMode ? unfocusedEditCursorColor : unfocusedNonEditCursorColor;
setNonFocusCursorColor(c);
}
boolean getEditMode() {
return editMode;
void updateColors() {
setFocusedCursorColor(isActiveComponent()
? getActiveColor()
: ByteViewerComponentProvider.CURSOR_COLOR_UNFOCUSED_NON_EDIT);
}
/**
@@ -700,28 +666,25 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene
model.dispose();
// scrollPane.getViewport().removeChangeListener(this);
layoutModel.dispose();
fieldFactories = null;
}
////////////////////////////////////////////////////////////////////////
/**
* Set up colors and mouse listener.
*/
private void initialize() {
setFont(ByteViewerComponentProvider.DEFAULT_FONT);
createFields();
setCursorOn(true);
editedTextColor = ByteViewerComponentProvider.EDITED_TEXT_COLOR;
setNonFocusCursorColor(ByteViewerComponentProvider.CURSOR_COLOR_UNFOCUSED_NON_EDIT);
setFocusedCursorColor(ByteViewerComponentProvider.CURSOR_COLOR_FOCUSED_NON_EDIT);
updateColorRunner = () -> updateFocusedColor();
addMouseListener(new MouseAdapter() {
@Override
public void mouseReleased(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON3) {
if (e.getButton() == MouseEvent.BUTTON3 && !isActiveComponent()) {
// hack to make sure that a right-clicked component becomes the active
// component
panel.setCurrentView(ByteViewerComponent.this);
@@ -729,7 +692,19 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene
}
});
setBackgroundColor(ByteViewerComponentProvider.BG_COLOR);
// specialized line coloring
setBackgroundColorModel(new ByteViewerBGColorModel(() -> getCursorLocation().getIndex()));
Gui.registerFont(this, ByteViewerComponentProvider.DEFAULT_FONT_ID);
invalidateModelFields();
enableHelp();
byteViewerHoverProvider =
new ByteViewerHoverProvider("ByteViewer" + model.getName() + "Hover");
setHoverProvider(byteViewerHoverProvider);
}
/**
@@ -747,15 +722,13 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene
*/
private void createFields() {
int fieldCount = bytesPerLine / model.getUnitByteSize();
int fieldCount = Math.max(bytesPerLine / model.getUnitByteSize(), 1);
fieldFactories = new FieldFactory[fieldCount];
int charWidth = fm.charWidth('W');
int fieldOffset = 0;
for (int i = 0; i < fieldCount; i++) {
fieldFactories[i] =
new FieldFactory(model, bytesPerLine, fieldOffset, fm, highlightProvider);
fieldFactories[i] = new FieldFactory(model, bytesPerLine, fieldOffset, charWidth, fm,
highlightProvider);
fieldOffset += model.getUnitByteSize();
fieldFactories[i].setEditColor(editedTextColor);
fieldFactories[i].setIndexMap(indexMap);
}
layoutModel.setFactorys(fieldFactories, model, charWidth);
@@ -904,56 +877,81 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene
return result;
}
private class ByteViewerBackgroundColorModel implements BackgroundColorModel {
/**
* Provides hover / tooltip popup for ByteViewer data models that implement
* {@link TooltipDataFormatModel}.
* <p>
* Typically HoverProviders rely on HoverServices (individually installed via plugins) that
* produce customized data for different components. This class just hardwires everything
* together.
*/
private class ByteViewerHoverProvider extends AbstractHoverProvider implements HoverService {
private Color defaultBackgroundColor = new GColor("color.bg.byteviewer");
@Override
public Color getBackgroundColor(BigInteger index) {
if (indexIsInCurrentLine(index)) {
return currentCursorLineColor;
}
return defaultBackgroundColor;
}
private boolean indexIsInCurrentLine(BigInteger layoutIndex) {
Field currentField = getCurrentField();
if (!(currentField instanceof ByteField)) {
// empty field
return false;
}
ByteField currentByteField = (ByteField) currentField;
BigInteger currentIndex = currentByteField.getIndex();
Layout layout = layoutModel.getLayout(layoutIndex);
int n = layout.getNumFields();
for (int i = 0; i < n; i++) {
Field field = layout.getField(i);
if (!(field instanceof ByteField)) {
continue;
}
ByteField byteField = (ByteField) field;
BigInteger fieldLayoutIndex = byteField.getIndex();
if (fieldLayoutIndex.equals(currentIndex)) {
return true;
}
}
return false;
public ByteViewerHoverProvider(String windowName) {
super(windowName);
addHoverService(this);
}
@Override
public Color getDefaultBackgroundColor() {
return defaultBackgroundColor;
protected ProgramLocation getHoverLocation(FieldLocation fieldLocation, Field field,
Rectangle fieldBounds, MouseEvent event) {
return model instanceof TooltipDataFormatModel && field instanceof ByteField
? new ProgramLocation()
: null;
}
@Override
public void setDefaultBackgroundColor(Color c) {
defaultBackgroundColor = c;
public int getPriority() {
return 0;
}
@Override
public boolean hoverModeSelected() {
return true;
}
@Override
public JComponent getHoverComponent(Program unusedProgram, ProgramLocation unusedProgLoc,
FieldLocation fieldLocation, Field field) {
if (!(field instanceof ByteField bf) ||
!(model instanceof TooltipDataFormatModel ttdfm)) {
return null;
}
BigInteger index = fieldLocation.getIndex();
ByteBlockInfo info = indexMap.getBlockInfo(index, bf.getFieldOffset());
if (info == null) {
return null;
}
String ttStr =
ttdfm.getTooltip(info.getBlock(), info.getOffset(), ByteViewerComponent.this);
if (ttStr != null && !ttStr.isBlank()) {
JToolTip tt = new JToolTip();
tt.setTipText(ttStr);
return tt;
}
return null;
}
@Override
public void componentHidden() {
// nothing
}
@Override
public void componentShown() {
// nothing
}
@Override
public void scroll(int amount) {
// WARNING: unusual situation. This method signature is the same between both
// AbstractHoverProvider and the HoverService interface.
// AbstractHoverProvider calls the scroll() on the service, but when
// both calls end up at the same method, you will get a stackoverflow.
// We implement a do-nothing here that prevents that.
}
}
}

View File

@@ -0,0 +1,24 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.byteviewer;
/**
* Allows components used as a column in the byteviewer to specify a more descriptive
* name to use for the header above the format's column.
*/
public interface ByteViewerComponentNamer {
String getByteViewerComponentName();
}

View File

@@ -17,16 +17,22 @@ package ghidra.app.plugin.core.byteviewer;
import static ghidra.GhidraOptions.*;
import java.awt.*;
import java.awt.Font;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.math.BigInteger;
import java.util.*;
import java.util.List;
import javax.swing.JComponent;
import javax.swing.KeyStroke;
import docking.action.ToggleDockingAction;
import generic.theme.GColor;
import generic.theme.GIcon;
import docking.*;
import docking.action.*;
import docking.action.builder.ActionBuilder;
import docking.action.builder.ToggleActionBuilder;
import docking.actions.PopupActionProvider;
import docking.widgets.fieldpanel.support.ViewerPosition;
import generic.theme.*;
import ghidra.GhidraOptions;
import ghidra.GhidraOptions.CURSOR_MOUSE_BUTTON_NAMES;
import ghidra.app.plugin.core.format.*;
@@ -37,11 +43,12 @@ import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.framework.plugintool.PluginTool;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
import ghidra.util.charset.CharsetInfo;
import ghidra.util.classfinder.ClassSearcher;
import ghidra.util.task.SwingUpdateManager;
public abstract class ByteViewerComponentProvider extends ComponentProviderAdapter
implements OptionsChangeListener {
implements OptionsChangeListener, PopupActionProvider {
protected static final String BLOCK_NUM = "Block Num";
protected static final String BLOCK_OFFSET = "Block Offset";
@@ -51,18 +58,16 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt
protected static final String Y_OFFSET = "Y Offset";
private static final String VIEW_NAMES = "View Names";
private static final String VIEW_WIDTHS = "View_Widths";
private static final String HEX_VIEW_GROUPSIZE = "Hex view groupsize";
private static final String BYTES_PER_LINE_NAME = "Bytes Per Line";
private static final String OFFSET_NAME = "Offset";
static final int DEFAULT_NUMBER_OF_CHARS = 8;
static final String DEFAULT_FONT_ID = "font.byteviewer";
static final int DEFAULT_BYTES_PER_LINE = 16;
static final Font DEFAULT_FONT = Gui.getFont(DEFAULT_FONT_ID);
static final String HEADER_FONT_ID = "font.byteviewer.header";
static final Font HEADER_FONT = Gui.getFont(HEADER_FONT_ID);
//@formatter:off
static final String FG = "byteviewer.color.fg";
static final String CURSOR = "byteviewer.color.cursor";
static final GColor FG_COLOR = new GColor("color.fg");
static final GColor BG_COLOR = new GColor("color.bg.byteviewer");
static final GColor SEPARATOR_COLOR = new GColor("color.fg.byteviewer.separator");
static final GColor EDITED_TEXT_COLOR = new GColor("color.fg.byteviewer.changed");
@@ -72,6 +77,8 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt
static final GColor CURSOR_COLOR_UNFOCUSED_NON_EDIT = new GColor("color.cursor.byteviewer.unfocused.non.edit");
static final GColor CURRENT_LINE_COLOR = GhidraOptions.DEFAULT_CURSOR_LINE_COLOR;
static final GColor HIGHLIGHT_COLOR = new GColor("color.bg.byteviewer.highlight");
static final GColor HIGHLIGHT_MIDDLE_MOUSE_COLOR = new GColor("color.bg.byteviewer.highlight.middle.mouse");
//@formatter:on
static final String INDEX_COLUMN_NAME = "Addresses";
@@ -91,19 +98,14 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt
private static final String OPTION_HIGHLIGHT_CURSOR_LINE =
GhidraOptions.HIGHLIGHT_CURSOR_LINE_OPTION_NAME;
private static final String OPTION_HIGHLIGHT_MIDDLE_MOUSE_NAME = "Middle Mouse Color";
private static final GColor HIGHLIGHT_MIDDLE_MOUSE_COLOR =
new GColor("color.bg.byteviewer.highlight.middle.mouse");
protected ByteViewerPanel panel;
private int bytesPerLine;
private int offset;
private int hexGroupSize = 1;
private ByteViewerConfigOptions configOptions = new ByteViewerConfigOptions();
protected Map<String, ByteViewerComponent> viewMap = new HashMap<>();
protected ToggleDockingAction editModeAction;
protected OptionsAction setOptionsAction;
protected ProgramByteBlockSet blockSet;
@@ -112,6 +114,9 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt
protected SwingUpdateManager updateManager;
private Map<String, Class<? extends DataFormatModel>> dataFormatModelClassMap;
private DockingAction shiftLeftAction;
private DockingAction shiftRightAction;
private DockingAction optionsAction;
protected ByteViewerComponentProvider(PluginTool tool, AbstractByteViewerPlugin<?> plugin,
String name, Class<?> contextType) {
@@ -122,8 +127,8 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt
initializedDataFormatModelClassMap();
panel = newByteViewerPanel();
bytesPerLine = DEFAULT_BYTES_PER_LINE;
setIcon(new GIcon("icon.plugin.byteviewer.provider"));
setOptions();
createActions();
@@ -132,6 +137,7 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt
addView(DEFAULT_VIEW);
setWindowMenuGroup("Byte Viewer");
tool.addPopupActionProvider(this);
}
protected ByteViewerPanel newByteViewerPanel() {
@@ -146,12 +152,75 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt
}
}
private void createActions() {
editModeAction = new ToggleEditAction(this, plugin);
setOptionsAction = new OptionsAction(this, plugin);
ToggleDockingAction getEditModeAction() {
// for junit
return editModeAction;
}
addLocalAction(editModeAction);
addLocalAction(setOptionsAction);
DockingAction getShiftLeftAction() {
// for junit
return shiftLeftAction;
}
DockingAction getShiftRightAction() {
// for junit
return shiftRightAction;
}
DockingAction getOptionsAction() {
// for junit
return optionsAction;
}
private void createActions() {
editModeAction =
new ToggleActionBuilder("Enable/Disable Byteviewer Editing", plugin.getName())
.selected(false)
.description("Enable/Disable editing of bytes in Byte Viewer panels.")
.toolBarIcon(new GIcon("icon.base.edit.bytes"))
.toolBarGroup("Byteviewer")
.keyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_E,
DockingUtils.CONTROL_KEY_MODIFIER_MASK | InputEvent.ALT_DOWN_MASK))
.enabledWhen(ac -> blockSet != null && blockSet.isValid())
.onAction(ac -> setEditMode(editModeAction.isSelected()))
.buildAndInstallLocal(this);
optionsAction = new ActionBuilder("Byte Viewer Options", plugin.getName())
.description("Set Byte Viewer Options")
.toolBarIcon(new GIcon("icon.plugin.byteviewer.options"))
.toolBarGroup("ZSettings")
.enabledWhen(ac -> blockSet != null && blockSet.isValid())
.onAction(ac -> tool.showDialog(
new ByteViewerOptionsDialog(ByteViewerComponentProvider.this),
ByteViewerComponentProvider.this))
.buildAndInstallLocal(this);
shiftLeftAction = new ActionBuilder("Shift Alignment Offset Left", plugin.getName())
.description("Shift Alignment Offset Left")
.popupMenuGroup("ByteOffsetShift")
.popupMenuPath("Shift Bytes Left")
.keyBinding("ctrl-comma")
.enabledWhen(ac -> blockSet != null && blockSet.isValid())
.onAction(ac -> adjustOffset(-1))
.buildAndInstallLocal(this);
shiftRightAction = new ActionBuilder("Shift Alignment Offset Right", plugin.getName())
.description("Shift Alignment Offset Right")
.popupMenuGroup("ByteOffsetShift")
.popupMenuPath("Shift Bytes Right")
.keyBinding("ctrl-period")
.enabledWhen(ac -> blockSet != null && blockSet.isValid())
.onAction(ac -> adjustOffset(+1))
.buildAndInstallLocal(this);
}
@Override
public List<DockingActionIf> getPopupActions(Tool tool, ActionContext context) {
if (context instanceof ByteViewerActionContext bvContext &&
bvContext.getComponentProvider() == this) {
return bvContext.getActiveColumn().getPopupActions(tool, bvContext);
}
return null;
}
@Override
@@ -179,12 +248,7 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt
@Override
public void optionsChanged(ToolOptions options, String optionName, Object oldValue,
Object newValue) {
if (options.getName().equals("ByteViewer")) {
if (optionName.equals(OPTION_FONT)) {
setFont((Font) newValue);
}
}
else if (options.getName().equals(CATEGORY_BROWSER_FIELDS)) {
if (options.getName().equals(CATEGORY_BROWSER_FIELDS)) {
if (optionName.equals(CURSOR_HIGHLIGHT_BUTTON_NAME)) {
CURSOR_MOUSE_BUTTON_NAMES mouseButton = (CURSOR_MOUSE_BUTTON_NAMES) newValue;
panel.setHighlightButton(mouseButton.getMouseEventID());
@@ -192,12 +256,6 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt
}
}
private void setFont(Font font) {
FontMetrics fm = panel.getFontMetrics(font);
panel.setFontMetrics(fm);
tool.setConfigChanged(true);
}
// Options.getStringEnum() is deprecated
private void setOptions() {
ToolOptions opt = tool.getOptions("ByteViewer");
@@ -238,13 +296,6 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt
opt.registerOption(OPTION_HIGHLIGHT_CURSOR_LINE, true, help,
"Toggles highlighting background color of line containing the cursor.");
Color separatorColor = opt.getColor(SEPARATOR_COLOR_OPTION_NAME, SEPARATOR_COLOR);
panel.setSeparatorColor(separatorColor);
Color middleMouseColor =
opt.getColor(OPTION_HIGHLIGHT_MIDDLE_MOUSE_NAME, HIGHLIGHT_MIDDLE_MOUSE_COLOR);
panel.setMouseButtonHighlightColor(middleMouseColor);
opt.addOptionsChangeListener(this);
// cursor highlight options
@@ -257,20 +308,24 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt
}
/**
* Set the offset that is applied to each block.
* @param blockOffset the new block offset
* Set the display offset that is applied to bytes in each block.
* <p>
* Changing this adjusts which byte appears first on each line of the grid.
*
* @param newOffset the new block offset (0..bytesPerLine-1)
*/
void setBlockOffset(int blockOffset) {
if (blockOffset == offset) {
return;
public void setOffset(int newOffset) {
if (configOptions.calcNormalizedOffset(newOffset) != configOptions.getOffset()) {
configOptions.setOffset(newOffset);
ViewerPosition vp = panel.getViewerPosition();
panel.updateLayoutConfigOptions(configOptions);
tool.setConfigChanged(true);
panel.setViewerPosition(vp);
}
int newOffset = blockOffset;
if (newOffset > bytesPerLine) {
newOffset = newOffset % bytesPerLine;
}
this.offset = newOffset;
panel.setOffset(newOffset);
tool.setConfigChanged(true);
}
void adjustOffset(int delta) {
setOffset(configOptions.getOffset() + delta);
}
ByteBlockInfo getCursorLocation() {
@@ -289,48 +344,105 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt
return blockSet;
}
/**
* Get the number of bytes displayed in a line.
* @return the number of bytes displayed in a line
*/
int getBytesPerLine() {
return bytesPerLine;
public ByteViewerConfigOptions getConfigOptions() {
return configOptions;
}
/**
* Get the offset that should be applied to each byte block.
* @return the offset that should be applied to each byte block
*/
int getOffset() {
return offset;
}
public void updateConfigOptions(ByteViewerConfigOptions newOptions, Set<String> selectedViews) {
Color getFocusedNonEditCursorColor() {
return CURSOR_COLOR_FOCUSED_NON_EDIT;
}
boolean changed = removeDeletedViews(selectedViews);
if (!configOptions.areOptionsEqual(newOptions)) {
changed = true;
int getGroupSize() {
return hexGroupSize;
}
boolean layoutChanged = configOptions.areLayoutParamsChanged(newOptions);
boolean widthsChanged = configOptions.areDislayWidthsChanged(newOptions);
void setGroupSize(int groupSize) {
if (groupSize == hexGroupSize) {
return;
}
hexGroupSize = groupSize;
ByteViewerComponent component = viewMap.get(HexFormatModel.NAME);
if (component != null) {
component.setGroupSize(groupSize);
component.invalidate();
configOptions = newOptions;
for (ByteViewerComponent bvc : viewMap.values()) {
bvc.getDataModel().setByteViewerConfigOptions(configOptions);
bvc.invalidateModelFields();
}
if (layoutChanged || widthsChanged) {
panel.updateLayoutConfigOptions(configOptions);
}
if (widthsChanged) {
panel.resetColumnsToDefaultWidths();
}
panel.invalidate();
panel.validate();
panel.repaint();
}
tool.setConfigChanged(true);
changed |= addNewViews(selectedViews);
if (changed) {
refreshView();
tool.setConfigChanged(true);
}
}
void setBytesPerLine(int bytesPerLine) {
if (this.bytesPerLine != bytesPerLine) {
this.bytesPerLine = bytesPerLine;
panel.setBytesPerLine(bytesPerLine);
private boolean removeDeletedViews(Set<String> selectedViews) {
if (selectedViews == null) {
return false;
}
boolean changed = false;
for (String viewName : getCurrentViews()) {
if (!selectedViews.contains(viewName)) {
removeView(viewName, true);
changed = true;
}
}
return changed;
}
private boolean addNewViews(Set<String> selectedViews) {
if (selectedViews == null) {
return false;
}
boolean changed = false;
Set<String> currentViews = getCurrentViews();
// add any missing views
for (String viewName : selectedViews) {
if (!currentViews.contains(viewName)) {
addView(viewName);
changed = true;
}
}
return changed;
}
private void updateModelConfig(String modelName) {
ByteViewerComponent bvc = viewMap.get(modelName);
if (bvc != null) {
bvc.getDataModel().setByteViewerConfigOptions(configOptions);
bvc.invalidateModelFields();
panel.repaint();
}
}
public void setCharsetInfo(CharsetInfo newCSI) {
CharsetInfo oldCSI = configOptions.getCharsetInfo();
if (!oldCSI.equals(newCSI)) {
configOptions.setCharsetInfo(newCSI);
// we know only Chars format cares about this setting
updateModelConfig(CharacterFormatModel.NAME);
if (oldCSI.getAlignment() != newCSI.getAlignment()) {
panel.resetColumnsToDefaultWidths();
}
tool.setConfigChanged(true);
}
}
public void setCompactChars(boolean newCompactChars) {
if (configOptions.isCompactChars() != newCompactChars) {
configOptions.setCompactChars(newCompactChars);
// we know only Chars format cares about this setting, and that it will change column width
updateModelConfig(CharacterFormatModel.NAME);
panel.resetColumnsToDefaultWidths();
tool.setConfigChanged(true);
}
}
@@ -338,9 +450,7 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt
protected void writeConfigState(SaveState saveState) {
List<String> viewNames = panel.getViewNamesInDisplayOrder();
saveState.putStrings(VIEW_NAMES, viewNames.toArray(new String[viewNames.size()]));
saveState.putInt(HEX_VIEW_GROUPSIZE, hexGroupSize);
saveState.putInt(BYTES_PER_LINE_NAME, bytesPerLine);
saveState.putInt(OFFSET_NAME, offset);
configOptions.write(saveState);
SaveState columnState = new SaveState(VIEW_WIDTHS);
int indexWidth = panel.getViewWidth(INDEX_COLUMN_NAME);
columnState.putInt(INDEX_COLUMN_NAME, indexWidth);
@@ -352,12 +462,13 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt
}
protected void readConfigState(SaveState saveState) {
configOptions.read(saveState);
String[] names = saveState.getStrings(VIEW_NAMES, new String[0]);
hexGroupSize = saveState.getInt(HEX_VIEW_GROUPSIZE, 1);
restoreViews(names, false);
bytesPerLine = saveState.getInt(BYTES_PER_LINE_NAME, DEFAULT_BYTES_PER_LINE);
offset = saveState.getInt(OFFSET_NAME, 0);
panel.restoreConfigState(bytesPerLine, offset);
panel.restoreConfigState(configOptions);
SaveState viewWidths = saveState.getSaveState(VIEW_WIDTHS);
if (viewWidths != null) {
String[] viewNames = viewWidths.getNames();
@@ -366,7 +477,6 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt
if (width > 0) {
panel.setViewWidth(viewName, width);
}
}
}
}
@@ -376,7 +486,7 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt
*/
private void restoreViews(String[] viewNames, boolean updateViewPosition) {
// clear existing views
for (String viewName : viewMap.keySet()) {
for (String viewName : List.copyOf(viewMap.keySet())) {
removeView(viewName, false);
}
for (String viewName : viewNames) {
@@ -400,9 +510,7 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt
private ByteViewerComponent addView(DataFormatModel model, boolean configChanged,
boolean updateViewPosition) {
if (model.getName().equals(HexFormatModel.NAME)) {
model.setGroupSize(hexGroupSize);
}
model.setByteViewerConfigOptions(configOptions);
String viewName = model.getName();
ByteViewerComponent bvc =
@@ -436,9 +544,12 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt
protected abstract void updateLiveSelection(ByteBlockSelection selection);
void dispose() {
tool.removePopupActionProvider(this);
updateManager.dispose();
updateManager = null;
panel.dispose();
if (blockSet != null) {
blockSet.dispose();
}
@@ -491,6 +602,12 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt
return names;
}
/**
* Factory, creates instances of DataFormatModel.
*
* @param formatName name
* @return new instance of the requested DataFormatModel
*/
public DataFormatModel getDataFormatModel(String formatName) {
Class<? extends DataFormatModel> classy = dataFormatModelClassMap.get(formatName);
if (classy == null) {

View File

@@ -0,0 +1,162 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.byteviewer;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import ghidra.framework.options.SaveState;
import ghidra.util.charset.CharsetInfo;
import ghidra.util.charset.CharsetInfoManager;
/**
* Configuration values for byte viewer data models, as well as the bytes_per_line of the
* byte viewer itself.
*/
public class ByteViewerConfigOptions {
static final int DEFAULT_BYTES_PER_LINE = 16;
private static final String HEX_VIEW_GROUPSIZE_OPTION_NAME = "Hex View Groupsize";
private static final String CHARSET_OPTION_NAME = "Charset Name";
private static final String COMPACTCHARS_OPTION_NAME = "Compact Chars";
private static final String USE_CHAR_ALIGNMENT_OPTION_NAME = "Use Char Alignment";
private static final String BYTES_PER_LINE_OPTION_NAME = "Bytes Per Line";
private static final String OFFSET_NAME = "Offset";
private int bytesPerLine = DEFAULT_BYTES_PER_LINE;
private int offset;
private boolean compactChars = true;
private boolean useCharAlignment = true;
private CharsetInfo csi = CharsetInfoManager.getInstance().get(StandardCharsets.US_ASCII);
private int hexGroupSize = 1;
public ByteViewerConfigOptions() {
// nothing
}
@Override
public ByteViewerConfigOptions clone() {
ByteViewerConfigOptions clone = new ByteViewerConfigOptions();
clone.bytesPerLine = bytesPerLine;
clone.compactChars = compactChars;
clone.useCharAlignment = useCharAlignment;
clone.csi = csi;
clone.hexGroupSize = hexGroupSize;
clone.offset = offset;
return clone;
}
public void read(SaveState saveState) {
hexGroupSize = saveState.getInt(HEX_VIEW_GROUPSIZE_OPTION_NAME, 1);
String charsetName = saveState.getString(CHARSET_OPTION_NAME, CharsetInfoManager.USASCII);
csi = CharsetInfoManager.getInstance().get(charsetName, StandardCharsets.US_ASCII);
compactChars = saveState.getBoolean(COMPACTCHARS_OPTION_NAME, true);
useCharAlignment = saveState.getBoolean(USE_CHAR_ALIGNMENT_OPTION_NAME, true);
bytesPerLine = saveState.getInt(BYTES_PER_LINE_OPTION_NAME, DEFAULT_BYTES_PER_LINE);
offset = saveState.getInt(OFFSET_NAME, 0);
}
public void write(SaveState saveState) {
saveState.putInt(HEX_VIEW_GROUPSIZE_OPTION_NAME, hexGroupSize);
saveState.putString(CHARSET_OPTION_NAME, csi.getName());
saveState.putBoolean(COMPACTCHARS_OPTION_NAME, compactChars);
saveState.putBoolean(USE_CHAR_ALIGNMENT_OPTION_NAME, useCharAlignment);
saveState.putInt(BYTES_PER_LINE_OPTION_NAME, bytesPerLine);
saveState.putInt(OFFSET_NAME, offset);
}
public boolean areOptionsEqual(ByteViewerConfigOptions other) {
return bytesPerLine == other.bytesPerLine && compactChars == other.compactChars &&
Objects.equals(csi, other.csi) && hexGroupSize == other.hexGroupSize &&
offset == other.offset && useCharAlignment == other.useCharAlignment;
}
public boolean areLayoutParamsChanged(ByteViewerConfigOptions other) {
return offset != other.getOffset() || hexGroupSize != other.getHexGroupSize() ||
bytesPerLine != other.getBytesPerLine() ||
useCharAlignment != other.isUseCharAlignment();
}
public boolean areDislayWidthsChanged(ByteViewerConfigOptions other) {
return getHexGroupSize() != other.getHexGroupSize() ||
isCompactChars() != other.isCompactChars() ||
(useCharAlignment && csi.getAlignment() != other.csi.getAlignment());
}
public int getBytesPerLine() {
return bytesPerLine;
}
public void setBytesPerLine(int newBytesPerLine) {
bytesPerLine = newBytesPerLine;
offset = Math.clamp(offset, 0, bytesPerLine - 1);
hexGroupSize = Math.clamp(hexGroupSize, 1, bytesPerLine);
}
public int getOffset() {
return offset;
}
public int calcNormalizedOffset(int newOffset) {
if (newOffset < 0) {
newOffset = bytesPerLine - 1;
}
else if (newOffset >= bytesPerLine) {
newOffset = newOffset % bytesPerLine;
}
return newOffset;
}
public void setOffset(int newOffset) {
offset = calcNormalizedOffset(newOffset);
}
public int getHexGroupSize() {
return hexGroupSize;
}
public void setHexGroupSize(int newHexGroupSize) {
hexGroupSize = newHexGroupSize;
}
public CharsetInfo getCharsetInfo() {
return csi;
}
public void setCharsetInfo(CharsetInfo newCSI) {
this.csi = newCSI;
}
public void setCompactChars(boolean newCompactChars) {
compactChars = newCompactChars;
}
public boolean isCompactChars() {
return compactChars;
}
public boolean isUseCharAlignment() {
return useCharAlignment;
}
public void setUseCharAlignment(boolean newUseCharAlignment) {
useCharAlignment = newUseCharAlignment;
}
}

View File

@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -18,13 +18,12 @@ package ghidra.app.plugin.core.byteviewer;
import java.awt.Color;
import docking.widgets.fieldpanel.support.Highlight;
import generic.theme.GColor;
class ByteViewerHighlighter {
private static Highlight[] NO_HIGHLIGHTS = new Highlight[0];
private String highlightText;
private Color highlightColor = new GColor("color.bg.byteviewer.highlight");
private Color highlightColor = ByteViewerComponentProvider.HIGHLIGHT_MIDDLE_MOUSE_COLOR;
public Highlight[] createHighlights(String text) {
@@ -41,8 +40,4 @@ class ByteViewerHighlighter {
String getText() {
return highlightText;
}
void setHighlightColor(Color color) {
this.highlightColor = color;
}
}

View File

@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -25,8 +25,6 @@ import javax.swing.*;
import docking.widgets.fieldpanel.FieldPanel;
import docking.widgets.fieldpanel.Layout;
import docking.widgets.indexedscrollpane.*;
import generic.theme.GColor;
import generic.theme.Gui;
import ghidra.app.plugin.core.format.DataFormatModel;
/**
@@ -42,7 +40,6 @@ import ghidra.app.plugin.core.format.DataFormatModel;
* client's responsibility to get the header component and install it into the IndexedScrollPane.
*/
class ByteViewerIndexedView extends JPanel implements IndexedScrollable, IndexScrollListener {
private static final String HEADER_FONT_ID = "font.byteviewer.header";
private FieldPanel indexPanel;
private List<FieldPanel> allPanels = new ArrayList<>();
private boolean processingIndexRangeChanged;
@@ -53,14 +50,12 @@ class ByteViewerIndexedView extends JPanel implements IndexedScrollable, IndexSc
this.indexPanel = indexPanel;
allPanels.add(indexPanel);
panelManager = new InteractivePanelManager();
panelManager.setHeaderFont(Gui.getFont(HEADER_FONT_ID));
indexPanel.addIndexScrollListener(this);
panelManager.addComponent(ByteViewerComponentProvider.INDEX_COLUMN_NAME, indexPanel);
JComponent mainPanel = panelManager.getMainPanel();
add(mainPanel, BorderLayout.CENTER);
mainPanel.setBackground(new GColor("color.bg.byteviewer"));
addMouseWheelListener(e -> {
// this lets us scroll the byte viewer when the user is not over any panel, but still

View File

@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -29,6 +29,7 @@ import docking.widgets.fieldpanel.listener.IndexMapper;
import docking.widgets.fieldpanel.listener.LayoutModelListener;
import docking.widgets.fieldpanel.support.SingleRowLayout;
import ghidra.app.plugin.core.format.DataFormatModel;
import ghidra.util.datastruct.ListenerSet;
/**
* Implements the LayoutModel for ByteViewer Components.
@@ -36,20 +37,18 @@ import ghidra.app.plugin.core.format.DataFormatModel;
public class ByteViewerLayoutModel implements LayoutModel {
private int width;
private IndexMap indexMap;
private List<LayoutModelListener> listeners;
private ListenerSet<LayoutModelListener> listeners =
new ListenerSet<>(LayoutModelListener.class, false);
private FieldFactory[] factorys;
private BigInteger numIndexes;
public ByteViewerLayoutModel() {
factorys = new FieldFactory[0];
listeners = new ArrayList<LayoutModelListener>(1);
numIndexes = BigInteger.ZERO;
}
void dispose() {
indexMap = null;
listeners = null;
factorys = null;
listeners.clear();
}
void setFactorys(FieldFactory[] fieldFactorys, DataFormatModel dataModel, int margin) {
@@ -75,31 +74,19 @@ public class ByteViewerLayoutModel implements LayoutModel {
return;
}
this.indexMap = indexMap;
if (indexMap == null) {
numIndexes = BigInteger.ZERO;
}
else {
numIndexes = indexMap.getNumIndexes();
}
indexSetChanged();
}
public void indexSetChanged() {
for (LayoutModelListener listener : listeners) {
listener.modelSizeChanged(IndexMapper.IDENTITY_MAPPER);
}
listeners.invoke().modelSizeChanged(IndexMapper.IDENTITY_MAPPER);
}
public void layoutChanged() {
for (LayoutModelListener listener : listeners) {
listener.dataChanged(BigInteger.ZERO, numIndexes);
}
listeners.invoke().dataChanged(BigInteger.ZERO, getNumIndexes());
}
public void dataChanged(BigInteger startIndex, BigInteger endIndex) {
for (LayoutModelListener listener : listeners) {
listener.dataChanged(startIndex, endIndex);
}
listeners.invoke().dataChanged(startIndex, endIndex);
}
@Override
@@ -117,12 +104,12 @@ public class ByteViewerLayoutModel implements LayoutModel {
*/
@Override
public BigInteger getNumIndexes() {
return numIndexes;
return indexMap != null ? indexMap.getNumIndexes() : BigInteger.ZERO;
}
@Override
public Layout getLayout(BigInteger index) {
if (index.compareTo(numIndexes) >= 0) {
if (index.compareTo(BigInteger.ZERO) < 0 || index.compareTo(getNumIndexes()) >= 0) {
return null;
}
List<Field> fields = new ArrayList<Field>(factorys.length);
@@ -158,17 +145,10 @@ public class ByteViewerLayoutModel implements LayoutModel {
listeners.add(listener);
}
/**
* @see docking.widgets.fieldpanel.LayoutModel#getIndexAfter(int)
*/
public int getIndexAfter(int index) {
return index + 1;
}
@Override
public BigInteger getIndexAfter(BigInteger index) {
BigInteger nextIndex = index.add(BigInteger.ONE);
if (nextIndex.compareTo(numIndexes) >= 0) {
if (nextIndex.compareTo(getNumIndexes()) >= 0) {
return null;
}
return nextIndex;
@@ -176,13 +156,15 @@ public class ByteViewerLayoutModel implements LayoutModel {
@Override
public BigInteger getIndexBefore(BigInteger index) {
BigInteger numIndexes = getNumIndexes();
if (numIndexes.compareTo(BigInteger.ZERO) <= 0 || index.compareTo(BigInteger.ZERO) <= 0) {
return null;
}
if (index.compareTo(numIndexes) > 0) {
return numIndexes.subtract(BigInteger.ONE);
}
BigInteger previousIndex = index.subtract(BigInteger.ONE);
if (previousIndex.compareTo(BigInteger.ZERO) < 0) {
return null;
}
return previousIndex;
}

View File

@@ -15,47 +15,55 @@
*/
package ghidra.app.plugin.core.byteviewer;
import java.awt.Component;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.*;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.math.BigInteger;
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.apache.commons.lang3.StringUtils;
import docking.DialogComponentProvider;
import docking.widgets.button.BrowseButton;
import docking.widgets.checkbox.GCheckBox;
import docking.widgets.fieldpanel.support.ViewerPosition;
import docking.widgets.label.GLabel;
import docking.widgets.spinner.IntegerSpinner;
import generic.theme.GThemeDefaults.Colors;
import generic.theme.GThemeDefaults.Colors.Messages;
import ghidra.app.plugin.core.format.DataFormatModel;
import ghidra.app.util.AddressInput;
import ghidra.app.util.bean.FixedBitSizeValueField;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.util.HelpLocation;
import ghidra.util.charset.CharsetInfo;
import ghidra.util.charset.picker.CharsetPickerDialog;
import ghidra.util.layout.PairLayout;
import ghidra.util.layout.VerticalLayout;
public class ByteViewerOptionsDialog extends DialogComponentProvider
implements ChangeListener, ActionListener {
public class ByteViewerOptionsDialog extends DialogComponentProvider {
private AddressInput addressInputField;
private FixedBitSizeValueField bytesPerLineField;
private FixedBitSizeValueField groupSizeField;
private JTextField charsetField;
private BrowseButton charsetPickerButton;
private ByteViewerComponentProvider provider;
private ByteViewerConfigOptions configOptions;
private Map<String, JCheckBox> checkboxMap = new HashMap<>();
private MySpinnerNumberModel bytesPerLineSpinnerModel;
private MySpinnerNumberModel offsetSpinnerModel;
private MySpinnerNumberModel hexGroupSizeSpinnerModel;
private IntegerSpinner bytesPerLineSpinner;
private IntegerSpinner offsetSpinner;
private IntegerSpinner hexGroupSizeSpinner;
private LinkedHashMap<String, DataFormatModel> models = new LinkedHashMap<>();
public ByteViewerOptionsDialog(ByteViewerComponentProvider provider) {
super("Byte Viewer Options");
this.provider = provider;
this.configOptions = provider.getConfigOptions().clone();
for (String modelName : provider.getDataFormatNames()) {
models.put(modelName, provider.getDataFormatModel(modelName));
}
addWorkPanel(buildPanel());
addOKButton();
addCancelButton();
@@ -65,258 +73,336 @@ public class ByteViewerOptionsDialog extends DialogComponentProvider
setRememberSize(false);
}
private void disposeModels() {
for (DataFormatModel model : models.values()) {
model.dispose();
}
models.clear();
}
private JComponent buildPanel() {
JPanel mainPanel = new JPanel(new VerticalLayout(10));
mainPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
mainPanel.add(buildSettingsPanel());
mainPanel.add(buildViewOptionsPanel());
setOkEnabled(hasValidFieldValues());
mainPanel.add(buildModelPickerPanel());
updateButtonEnablement();
return mainPanel;
}
private void setTooltip(IntegerSpinner spinner, String text) {
spinner.getTextField().getComponent().setToolTipText(text);
spinner.getSpinner().setToolTipText(text);
}
private void updateModelStatus(DataFormatModel model, String errorMsg) {
JCheckBox cb = checkboxMap.get(model.getName());
if (cb != null) {
cb.setForeground(errorMsg == null ? Colors.FOREGROUND : Messages.ERROR);
cb.setToolTipText(errorMsg);
}
}
private boolean isModelEnabled(DataFormatModel model) {
JCheckBox cb = checkboxMap.get(model.getName());
return cb != null && cb.isSelected();
}
private void updateButtonEnablement() {
int enabledModelCount = 0;
String firstErrorMsg = null;
for (DataFormatModel model : models.values()) {
String errorMsg = model.validateByteViewerConfigOptions(configOptions);
if (errorMsg == null &&
configOptions.getBytesPerLine() % model.getUnitByteSize() != 0) {
errorMsg = "%s (%d bytes) is not a multiple of %d".formatted(model.getName(),
model.getUnitByteSize(), configOptions.getBytesPerLine());
}
updateModelStatus(model, errorMsg);
if (isModelEnabled(model)) {
enabledModelCount++;
if (errorMsg != null) {
firstErrorMsg = firstErrorMsg == null ? errorMsg : firstErrorMsg;
}
}
}
if (enabledModelCount == 0) {
firstErrorMsg = "You must have at least one view selected";
}
setStatusText(firstErrorMsg);
setOkEnabled(firstErrorMsg == null);
}
private Component buildSettingsPanel() {
JPanel panel = new JPanel(new PairLayout(5, 5));
panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
panel.add(new GLabel("Alignment Address:"));
buildAddressField(panel);
bytesPerLineSpinnerModel =
new MySpinnerNumberModel(configOptions.getBytesPerLine(), 1, 256, 1);
offsetSpinnerModel = new MySpinnerNumberModel(configOptions.getOffset(), 0,
configOptions.getBytesPerLine() - 1, 1) {
// wrap around the top and bottom of the valid range
@Override
public Object getNextValue() {
Long val = (Long) getNumber();
Long maximum = (Long) getMaximum();
if (maximum != null && maximum.compareTo(val) <= 0) {
return getMinimum();
}
return super.getNextValue();
}
@Override
public Object getPreviousValue() {
Long val = (Long) getNumber();
Long min = (Long) getMinimum();
if (min != null && min.compareTo(val) >= 0) {
return getMaximum();
}
return super.getPreviousValue();
}
};
hexGroupSizeSpinnerModel = new MySpinnerNumberModel(configOptions.getHexGroupSize(), 1,
configOptions.getBytesPerLine(), 1);
bytesPerLineSpinner = new MyIntegerSpinner(bytesPerLineSpinnerModel, 3);
bytesPerLineSpinner.getTextField().setShowNumberMode(false);
bytesPerLineSpinner.getTextField().setAccessibleName("Bytes Per Line");
setTooltip(bytesPerLineSpinner, "Number of bytes to display in each row of the viewer.");
offsetSpinner = new MyIntegerSpinner(offsetSpinnerModel, 3);
offsetSpinner.getTextField().setShowNumberMode(false);
offsetSpinner.getTextField().setAccessibleName("Offset");
setTooltip(offsetSpinner, "Adjusts the starting byte of the row left or right.\n" +
"Ranges from 0 to bytes per line (exclusive).");
hexGroupSizeSpinner = new MyIntegerSpinner(hexGroupSizeSpinnerModel, 3);
hexGroupSizeSpinner.getTextField().setShowNumberMode(false);
hexGroupSizeSpinner.getTextField().setAccessibleName("Hex Group Size");
setTooltip(hexGroupSizeSpinner,
"How many bytes will be grouped together in the hex view.\n" +
"Ranges from 1 to bytes per line (inclusive).");
bytesPerLineSpinnerModel.addChangeListener(e -> {
Long bpl = (Long) bytesPerLineSpinnerModel.getNumber();
if (bpl != null) {
setBytesPerLine(bpl.intValue());
}
});
offsetSpinnerModel.addChangeListener(e -> {
Long val = (Long) offsetSpinnerModel.getNumber();
if (val != null) {
setOffset(val.intValue());
}
});
hexGroupSizeSpinnerModel.addChangeListener(e -> {
Long val = (Long) hexGroupSizeSpinnerModel.getNumber();
if (val != null) {
setHexGroupSize(val.intValue());
}
});
panel.add(new GLabel("Bytes Per Line:"));
bytesPerLineField = new FixedBitSizeValueField(8, false, true);
bytesPerLineField.setFormat(10, false);
bytesPerLineField.setMinMax(BigInteger.valueOf(1), BigInteger.valueOf(256));
bytesPerLineField.setValue(BigInteger.valueOf(provider.getBytesPerLine()));
panel.add(bytesPerLineField);
bytesPerLineField.addChangeListener(this);
bytesPerLineField.getAccessibleContext().setAccessibleName("Bytes Per Line");
panel.add(bytesPerLineSpinner.getSpinner());
panel.add(new GLabel("Group size (Hex View Only):"));
groupSizeField = new FixedBitSizeValueField(8, false, true);
groupSizeField.setFormat(10, false);
groupSizeField.setMinMax(BigInteger.valueOf(1), BigInteger.valueOf(256));
groupSizeField.setValue(BigInteger.valueOf(provider.getGroupSize()));
panel.add(groupSizeField);
groupSizeField.addChangeListener(this);
groupSizeField.getAccessibleContext().setAccessibleName("Group Size");
panel.add(new GLabel("Alignment Offset:"));
panel.add(offsetSpinner.getSpinner());
panel.add(new GLabel("Hex Group Size:"));
panel.add(hexGroupSizeSpinner.getSpinner());
panel.add(new GLabel("Charset:"));
charsetField = new JTextField();
charsetField.setEditable(false);
charsetField.setText(configOptions.getCharsetInfo().getName());
charsetField.getAccessibleContext().setAccessibleName("Character Set Name");
charsetPickerButton = new BrowseButton();
charsetPickerButton.addActionListener(e -> pickCharset());
charsetPickerButton.getAccessibleContext().setAccessibleName("Character Set Picker");
JPanel charsetPanel = new JPanel(new BorderLayout());
charsetPanel.add(charsetField, BorderLayout.CENTER);
charsetPanel.add(charsetPickerButton, BorderLayout.EAST);
panel.add(charsetPanel);
panel.add(new GLabel("Compact Char Width:"));
GCheckBox compactChars = new GCheckBox();
compactChars.setSelected(configOptions.isCompactChars());
compactChars.addChangeListener(e -> {
configOptions.setCompactChars(compactChars.isSelected());
// doesn't affect ok enablement, no need to call updateButtonEnablement()
});
compactChars.getAccessibleContext().setAccessibleName("Compact Characters");
compactChars.setToolTipText(
"Display characters tightly packed together or more widely spaced apart");
panel.add(compactChars);
panel.add(new GLabel("Use Char Alignment:"));
GCheckBox useCharAlignment = new GCheckBox();
useCharAlignment.setSelected(configOptions.isUseCharAlignment());
useCharAlignment.addChangeListener(e -> {
configOptions.setUseCharAlignment(useCharAlignment.isSelected());
// doesn't affect ok enablement, no need to call updateButtonEnablement()
});
useCharAlignment.getAccessibleContext().setAccessibleName("Character Alignment");
useCharAlignment
.setToolTipText("Align start-of-character location with charset's byte width.\n" +
"Only some charsets (like UTF-16/UTF-32) are marked as alignable.");
panel.add(useCharAlignment);
return panel;
}
private void buildAddressField(JPanel parentPanel) {
if (!(provider instanceof ProgramByteViewerComponentProvider programProvider)) {
buildSimpleAddressInput(parentPanel);
return;
void setBytesPerLine(int bpl) {
configOptions.setBytesPerLine(bpl);
if (configOptions.getOffset() != offsetSpinnerModel.getNumber().intValue()) {
offsetSpinnerModel.setValue(Long.valueOf(configOptions.getOffset()));
}
Program program = programProvider.getProgram();
if (program == null) {
buildSimpleAddressInput(parentPanel);
return;
if (configOptions.getHexGroupSize() != hexGroupSizeSpinnerModel.getIntValue()) {
hexGroupSizeSpinnerModel.setValue(Long.valueOf(configOptions.getHexGroupSize()));
}
offsetSpinnerModel.setMaximum(Long.valueOf(bpl - 1));
hexGroupSizeSpinnerModel.setMaximum(Long.valueOf(bpl));
Address alignment = getAlignmentAddress();
if (alignment == null) {
buildSimpleAddressInput(parentPanel);
return;
}
addressInputField = new AddressInput(program, a -> update());
addressInputField.setAccessibleName("Alignment Address");
addressInputField.setAddressSpaceFilter(s -> s == alignment.getAddressSpace());
addressInputField.setAddress(alignment);
parentPanel.add(addressInputField);
updateButtonEnablement();
}
private void buildSimpleAddressInput(JPanel parentPanel) {
addressInputField = new AddressInput();
addressInputField.setAccessibleName("Alignment Address");
addressInputField.setEnabled(false);
parentPanel.add(addressInputField);
public void setOffset(int val) {
configOptions.setOffset(val);
updateButtonEnablement();
}
private Component buildViewOptionsPanel() {
void setHexGroupSize(int val) {
configOptions.setHexGroupSize(val);
updateButtonEnablement();
}
private void pickCharset() {
CharsetInfo newCSI = CharsetPickerDialog.pickCharset(configOptions.getCharsetInfo());
if (newCSI != null) {
setCharsetInfo(newCSI);
}
}
private void setCharsetInfo(CharsetInfo newCSI) {
configOptions.setCharsetInfo(newCSI);
charsetField.setText(newCSI.getName());
}
private JPanel buildModelPickerPanel() {
JPanel panel = new JPanel(new GridLayout(0, 2, 40, 0));
Border outer = BorderFactory.createTitledBorder("Views");
Border inner = BorderFactory.createEmptyBorder(5, 15, 5, 15);
panel.setBorder(BorderFactory.createCompoundBorder(outer, inner));
Set<String> currentViews = provider.getCurrentViews();
List<String> dataModelNames = provider.getDataFormatNames();
for (String formatName : dataModelNames) {
GCheckBox checkBox = new GCheckBox(formatName);
checkBox.addActionListener(this);
checkboxMap.put(formatName, checkBox);
if (currentViews.contains(formatName)) {
checkBox.setSelected(true);
for (DataFormatModel model : models.values()) {
String modelName = model.getName();
GCheckBox cb = new GCheckBox(modelName);
cb.addChangeListener(e -> updateButtonEnablement());
checkboxMap.put(modelName, cb);
if (currentViews.contains(modelName)) {
cb.setSelected(true);
}
panel.add(checkBox);
panel.add(cb);
}
return panel;
}
private Address getAlignmentAddress() {
int bytesPerLine = provider.getBytesPerLine();
int offset = provider.getOffset();
Program program = ((ProgramByteViewerComponentProvider) provider).getProgram();
Address minAddr = program.getMinAddress();
if (minAddr == null) {
return null;
}
long addressOffset = minAddr.getOffset() + offset;
int alignment = (int) (addressOffset % bytesPerLine);
return (alignment == 0) ? minAddr : minAddr.add(bytesPerLine - alignment);
void setModelSelected(String modelName, boolean selected) {
JCheckBox cb = checkboxMap.get(modelName);
cb.setSelected(selected);
}
@Override
protected void okCallback() {
int bytesPerLine = bytesPerLineField.getValue().intValue();
int addrOffset = 0;
Address alignmentAddress = addressInputField.getAddress();
if (alignmentAddress != null) {
addrOffset = (int) (alignmentAddress.getOffset() % bytesPerLine);
}
// We want the alignment address to begin a column, so subtract addrOffset from bytesPerLine
int offset = addrOffset == 0 ? 0 : bytesPerLine - addrOffset;
int groupSize = groupSizeField.getValue().intValue();
removeDeletedViews();
// Setting these properties individually is problematic since it can temporarily put the
// system into a bad state. As a hack, set the bytes per line to 256 since that can support
// all allowed group sizes. Then set the group first since there will be a divide by zero
// exception if the group size is ever bigger than the bytes per line. Finally, after all
// setting have been updated, add in the newly added views.
provider.setBytesPerLine(256);
provider.setGroupSize(groupSize);
provider.setBytesPerLine(bytesPerLine);
provider.setBlockOffset(offset);
addNewViews();
ViewerPosition vp = provider.getByteViewerPanel().getViewerPosition();
provider.updateConfigOptions(configOptions, getSelectedViewNames());
provider.getByteViewerPanel().setViewerPosition(vp);
disposeModels();
close();
}
private void removeDeletedViews() {
Set<String> currentViews = provider.getCurrentViews();
for (String viewName : currentViews) {
JCheckBox checkBox = checkboxMap.get(viewName);
if (!checkBox.isSelected()) {
provider.removeView(viewName, true);
}
}
}
private void addNewViews() {
Set<String> currentViews = provider.getCurrentViews();
// now add any views that have been selected
for (String viewName : checkboxMap.keySet()) {
JCheckBox checkBox = checkboxMap.get(viewName);
if (!currentViews.contains(viewName) && checkBox.isSelected()) {
provider.addView(viewName);
}
}
}
@Override
public void actionPerformed(ActionEvent e) {
update();
protected void cancelCallback() {
disposeModels();
super.cancelCallback();
}
@Override
public void stateChanged(ChangeEvent e) {
update();
private Set<String> getSelectedViewNames() {
return checkboxMap.entrySet()
.stream()
.filter(entry -> entry.getValue().isSelected())
.map(entry -> entry.getKey())
.collect(Collectors.toSet());
}
private void update() {
setOkEnabled(hasValidFieldValues());
}
private static class MySpinnerNumberModel extends SpinnerNumberModel {
private boolean hasValidFieldValues() {
if (!validateAddress()) {
return false;
public MySpinnerNumberModel(int value, int minimum, int maximum, int stepSize) {
super(Long.valueOf(value), Long.valueOf(minimum), Long.valueOf(maximum),
Long.valueOf(stepSize));
}
BigInteger bytesPerLine = bytesPerLineField.getValue();
if (bytesPerLine == null) {
setStatusText("Enter a value for Bytes Per Line");
return false;
public int getIntValue() {
return ((Long) getValue()).intValue();
}
BigInteger groupSize = groupSizeField.getValue();
if (groupSize == null) {
setStatusText("Enter a group size");
return false;
}
if (bytesPerLine.intValue() % groupSize.intValue() != 0) {
setStatusText("The bytes per line must be a multiple of the group size.");
return false;
public boolean isValid(Object value) {
if (value == null || !(value instanceof Long val)) {
return false;
}
Long minimum = (Long) getMinimum();
Long maximum = (Long) getMaximum();
if (minimum.compareTo(val) > 0 || maximum.compareTo(val) < 0) {
return false;
}
return true;
}
if (checkForUnsupportedModels(bytesPerLine.intValue())) {
setStatusText("Not all selected views support the current bytes per line value.");
return false;
}
if (!atLeastOneViewOn()) {
setStatusText("You must have at least one view selected");
return false;
}
setStatusText("");
return true;
}
private boolean validateAddress() {
if (!addressInputField.isEnabled()) {
return true; // nothing to validate
}
String addrText = addressInputField.getText();
if (StringUtils.isBlank(addrText)) {
setStatusText("Enter an alignment address");
return false;
}
Address alignmentAddress = addressInputField.getAddress();
if (alignmentAddress == null) {
setStatusText("Invalid alignment address:" + addrText);
return false;
}
return true;
}
private boolean atLeastOneViewOn() {
Set<Entry<String, JCheckBox>> entrySet = checkboxMap.entrySet();
for (Entry<String, JCheckBox> entry : entrySet) {
JCheckBox checkBox = entry.getValue();
if (checkBox.isSelected()) {
return true;
@Override
public void setValue(Object value) {
if (isValid(value)) {
super.setValue(value);
}
}
return false;
}
private boolean checkForUnsupportedModels(int bytesPerLine) {
boolean isBad = false;
Set<Entry<String, JCheckBox>> entrySet = checkboxMap.entrySet();
for (Entry<String, JCheckBox> entry : entrySet) {
JCheckBox checkBox = entry.getValue();
DataFormatModel model = provider.getDataFormatModel(entry.getKey());
if (model.validateBytesPerLine(bytesPerLine)) {
checkBox.setForeground(Colors.FOREGROUND);
}
else {
checkBox.setForeground(Messages.ERROR);
isBad |= checkBox.isSelected();
}
private static class MyIntegerSpinner extends IntegerSpinner {
// change color of text field to red if value that the user manually entered conflicts with
// spinner model, and when the focus leaves the text field, validate the text and replace
// it with the current model value if invalid
MyIntegerSpinner(MySpinnerNumberModel spinnerModel, int columns) {
super(spinnerModel, columns);
integerTextField.addChangeListener(e -> {
BigInteger valObj = integerTextField.getValue();
Long value = valObj != null ? valObj.longValue() : null;
integerTextField.getComponent()
.setForeground(
spinnerModel.isValid(value) ? Colors.FOREGROUND : Messages.ERROR);
});
integerTextField.getComponent().addFocusListener(new FocusListener() {
@Override
public void focusLost(FocusEvent e) {
BigInteger valObj = integerTextField.getValue();
Long value = valObj != null ? valObj.longValue() : null;
if (!spinnerModel.isValid(value)) {
integerTextField.setValue(spinnerModel.getIntValue());
}
}
@Override
public void focusGained(FocusEvent e) {
// nothing
}
});
}
return isBad;
}
}

View File

@@ -21,7 +21,9 @@ import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
import javax.swing.border.BevelBorder;
import docking.widgets.EventTrigger;
import docking.widgets.fieldpanel.*;
import docking.widgets.fieldpanel.field.EmptyTextField;
import docking.widgets.fieldpanel.field.Field;
@@ -36,7 +38,7 @@ import ghidra.app.util.viewer.listingpanel.AddressSetDisplayListener;
import ghidra.program.model.address.AddressSet;
import ghidra.program.model.address.AddressSetView;
import ghidra.util.*;
import ghidra.util.exception.InvalidInputException;
import ghidra.util.datastruct.ListenerSet;
import ghidra.util.layout.PairLayout;
import help.Help;
import help.HelpService;
@@ -63,20 +65,19 @@ public class ByteViewerPanel extends JPanel implements LayoutModel, LayoutListen
private IndexMap indexMap; // maps indexes to the correct block and offset
private int blockOffset;
private ByteViewerComponent currentView;
private Color highlightColor;
private int highlightButton;
private List<LayoutModelListener> layoutListeners = new ArrayList<>(1);
private ListenerSet<LayoutModelListener> layoutListeners =
new ListenerSet<>(LayoutModelListener.class, false);
private boolean addingView; // don't respond to cursor location changes while this flag is true
private final ByteViewerComponentProvider provider;
private List<AddressSetDisplayListener> displayListeners = new ArrayList<>();
private ByteViewerIndexedView indexedView;
private boolean editMode;
protected ByteViewerPanel(ByteViewerComponentProvider provider) {
super();
this.provider = provider;
bytesPerLine = ByteViewerComponentProvider.DEFAULT_BYTES_PER_LINE;
bytesPerLine = provider.getConfigOptions().getBytesPerLine();
viewList = new ArrayList<>();
indexMap = new IndexMap();
create();
@@ -106,30 +107,18 @@ public class ByteViewerPanel extends JPanel implements LayoutModel, LayoutListen
return new Dimension(width, height);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
void updateColors() {
for (ByteViewerComponent comp : viewList) {
comp.updateColors();
}
}
int getHighlightButton() {
return highlightButton;
}
void setHighlightButton(int highlightButton) {
this.highlightButton = highlightButton;
for (ByteViewerComponent comp : viewList) {
comp.setHighlightButton(highlightButton);
}
}
void setMouseButtonHighlightColor(Color color) {
this.highlightColor = color;
for (ByteViewerComponent comp : viewList) {
comp.setMouseButtonHighlightColor(color);
}
}
void setSeparatorColor(Color c) {
indexFactory.setMissingValueColor(c);
for (ByteViewerComponent comp : viewList) {
comp.setSeparatorColor(c);
}
}
/**
@@ -151,12 +140,17 @@ public class ByteViewerPanel extends JPanel implements LayoutModel, LayoutListen
ByteBlock lastBlock = blocks[blocks.length - 1];
endField.setText(lastBlock
.getLocationRepresentation(lastBlock.getLength().subtract(BigInteger.ONE)));
offsetField.setText(Integer.toString(blockOffset));
clearSelection();
}
}
if (indexMap == null) {
indexMap = new IndexMap();
startField.setText("00000000");
endField.setText("00000000");
offsetField.setText("00000000");
insertionField.setText("00000000");
}
indexFactory.setIndexMap(indexMap);
indexFactory.setSize(getIndexSizeInChars());
@@ -198,12 +192,6 @@ public class ByteViewerPanel extends JPanel implements LayoutModel, LayoutListen
}
}
public void setViewerBackgroundColorModel(BackgroundColorModel colorModel) {
for (ByteViewerComponent c : viewList) {
c.setBackgroundColorModel(colorModel);
}
}
/**
* Get the current highlight.
*
@@ -220,7 +208,6 @@ public class ByteViewerPanel extends JPanel implements LayoutModel, LayoutListen
* Called by the plugin in response to an event.
*/
void setCursorLocation(ByteBlock block, BigInteger index, int column) {
int modelIndex = -1;
for (ByteViewerComponent c : viewList) {
modelIndex = c.setViewerCursorLocation(block, index, column);
@@ -228,6 +215,13 @@ public class ByteViewerPanel extends JPanel implements LayoutModel, LayoutListen
if (modelIndex >= 0) {
insertionField.setText(block.getLocationRepresentation(index));
}
updateIndexColumnCurrentLine();
}
void updateIndexColumnCurrentLine() {
// this needs to be called by each ByteViewerComponent when the line index for their
// cursor changes so that the address column can be updated
indexPanel.repaint();
}
/**
@@ -257,9 +251,17 @@ public class ByteViewerPanel extends JPanel implements LayoutModel, LayoutListen
return currentView;
}
public ByteViewerComponent getComponentByName(String name) {
for (ByteViewerComponent bvc : viewList) {
if (name.equals(bvc.getDataModel().getName())) {
return bvc;
}
}
return null;
}
protected ByteViewerComponent newByteViewerComponent(DataFormatModel model) {
return new ByteViewerComponent(this, new ByteViewerLayoutModel(), model, bytesPerLine,
fontMetrics);
return new ByteViewerComponent(this, new ByteViewerLayoutModel(), model, bytesPerLine);
}
/**
@@ -281,11 +283,7 @@ public class ByteViewerPanel extends JPanel implements LayoutModel, LayoutListen
ViewerPosition vp = getViewerPosition();
ByteViewerComponent c = newByteViewerComponent(model);
c.setEditMode(editMode);
c.setIndexMap(indexMap);
c.setMouseButtonHighlightColor(highlightColor);
c.setHighlightButton(highlightButton);
viewList.add(c);
c.setSize(c.getPreferredSize());
indexedView.addView(viewName, c);
@@ -318,6 +316,7 @@ public class ByteViewerPanel extends JPanel implements LayoutModel, LayoutListen
}
addingView = false;
}
c.updateColors();
validate();
repaint();
return c;
@@ -329,12 +328,9 @@ public class ByteViewerPanel extends JPanel implements LayoutModel, LayoutListen
indexedView.removeView(comp);
if (currentView == comp) {
currentView = null;
currentView = !viewList.isEmpty() ? viewList.get(0) : null;
}
if (viewList.size() > 0) {
currentView = viewList.get(0);
}
comp.dispose();
validate();
repaint();
@@ -342,19 +338,16 @@ public class ByteViewerPanel extends JPanel implements LayoutModel, LayoutListen
void setCurrentView(ByteViewerComponent c) {
currentView = c;
updateColors();
}
void setEditMode(boolean editMode) {
for (ByteViewerComponent c : viewList) {
c.setEditMode(editMode);
}
this.editMode = editMode;
updateColors();
}
boolean getEditMode() {
if (currentView == null) {
return false;
}
return currentView.getEditMode();
return editMode;
}
/**
@@ -366,25 +359,23 @@ public class ByteViewerPanel extends JPanel implements LayoutModel, LayoutListen
}
}
int getNumberOfViews() {
return viewList.size();
}
void updateLayoutConfigOptions(ByteViewerConfigOptions options) {
boolean bplChanged = bytesPerLine != options.getBytesPerLine();
boolean offsetChanged = blockOffset != options.getOffset();
if (bplChanged || offsetChanged) {
bytesPerLine = options.getBytesPerLine();
blockOffset = options.getOffset();
void setOffset(int offset) {
if (blockOffset != offset) {
blockOffset = offset;
updateIndexMap();
offsetField.setText(Integer.toString(offset));
offsetField.setText(Integer.toString(blockOffset));
}
if (bplChanged) {
// reset view column widths to preferred width for new bytesPerline
resetColumnsToDefaultWidths();
}
}
void setBytesPerLine(int bytesPerLine) {
if (this.bytesPerLine != bytesPerLine) {
this.bytesPerLine = bytesPerLine;
updateIndexMap();
}
// reset view column widths to preferred width for new bytesPerline
void resetColumnsToDefaultWidths() {
indexedView.resetViewWidthToDefaults();
// force everything to get validated, or else the
@@ -394,47 +385,6 @@ public class ByteViewerPanel extends JPanel implements LayoutModel, LayoutListen
repaint();
}
/**
* Check that each model for the views can support the given bytes per line value.
* @param numBytesPerLine the bytes per line value to see if supported
*
* @throws InvalidInputException if a model cannot support the bytesPerLine value
*/
void checkBytesPerLine(int numBytesPerLine) throws InvalidInputException {
for (ByteViewerComponent c : viewList) {
DataFormatModel model = c.getDataModel();
int groupSize = model.getGroupSize();
if (groupSize > 0) {
if (numBytesPerLine % groupSize != 0) {
throw new InvalidInputException(
"Bytes Per Line not divisible by Group Size[" + groupSize + "].");
}
}
}
}
/**
* Set the group size on the current view.
*
* @param groupSize new group size
*/
void setCurrentGroupSize(int groupSize) {
if (currentView == null) {
return;
}
ByteBlockInfo info = currentView.getViewerCursorLocation();
currentView.setGroupSize(groupSize);
if (info != null) {
setCursorLocation(info.getBlock(), info.getOffset(), info.getColumn());
}
// force everything to get validated, or else the
// header columns do not get repainted properly...
invalidate();
validate();
repaint();
}
/**
* Set the insertion field and tell other views to change location; called when the
* ByteViewerComponent receives a notification that the cursor location has changed.
@@ -448,7 +398,6 @@ public class ByteViewerPanel extends JPanel implements LayoutModel, LayoutListen
*/
void setInsertionField(ByteViewerComponent source, ByteBlock block, BigInteger offset,
BigInteger modelIndex, int column, boolean isAltDown) {
provider.updateLocation(block, offset, column, isAltDown);
if (addingView) {
@@ -468,6 +417,16 @@ public class ByteViewerPanel extends JPanel implements LayoutModel, LayoutListen
}
c.setViewerCursorLocation(block, offset, column);
}
updateIndexColumnCurrentLine();
}
void setCurrentNonMappedIndex(BigInteger index, ByteViewerComponent source) {
// used to update all viewer columns to a line index that isn't mapped to a byte offset
for (ByteViewerComponent c : viewList) {
if (c != source) {
c.setCursorPosition(index, 0, 0, 0, EventTrigger.INTERNAL_ONLY);
}
}
}
/**
@@ -490,10 +449,7 @@ public class ByteViewerPanel extends JPanel implements LayoutModel, LayoutListen
void updateLiveSelection(ByteViewerComponent source, ByteBlockSelection selection) {
provider.updateLiveSelection(selection);
}
FontMetrics getCurrentFontMetrics() {
return fontMetrics;
updateIndexColumnCurrentLine();
}
List<String> getViewNamesInDisplayOrder() {
@@ -528,15 +484,8 @@ public class ByteViewerPanel extends JPanel implements LayoutModel, LayoutListen
indexPanel.setViewerPosition(vpos.getIndex(), vpos.getXOffset(), vpos.getYOffset());
}
void restoreConfigState(int newBytesPerLine, int offset) {
if (blockOffset != offset) {
blockOffset = offset;
offsetField.setText(Integer.toString(offset));
if (this.bytesPerLine == newBytesPerLine) {
updateIndexMap();
}
}
setBytesPerLine(newBytesPerLine);
void restoreConfigState(ByteViewerConfigOptions options) {
updateLayoutConfigOptions(options);
}
void programWasRestored() {
@@ -544,34 +493,29 @@ public class ByteViewerPanel extends JPanel implements LayoutModel, LayoutListen
refreshView();
}
void setFontMetrics(FontMetrics fm) {
this.fontMetrics = fm;
for (ByteViewerComponent c : viewList) {
c.setFontMetrics(fm);
}
indexFactory = new IndexFieldFactory(fm);
indexFactory.setSize(getIndexSizeInChars());
updateIndexMap();
indexPanel.modelSizeChanged(IndexMapper.IDENTITY_MAPPER);
}
protected FontMetrics getFontMetrics() {
return fontMetrics;
}
protected int getBytesPerLine() {
return bytesPerLine;
}
void dispose() {
for (ByteViewerComponent comp : viewList) {
comp.dispose();
}
viewList.clear();
indexMap = new IndexMap();
blockSet = null;
layoutListeners.clear();
}
/**
* Create the components for this top level panel.
*/
private void create() {
setLayout(new BorderLayout(10, 0));
fontMetrics = getFontMetrics(Gui.getFont(ByteViewerComponentProvider.DEFAULT_FONT_ID));
fontHeight = fontMetrics.getHeight();
setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED));
setFont(ByteViewerComponentProvider.DEFAULT_FONT); // side-effect sets fontMetrics
// for the index/address column
indexFactory = new IndexFieldFactory(fontMetrics);
@@ -581,16 +525,22 @@ public class ByteViewerPanel extends JPanel implements LayoutModel, LayoutListen
indexPanel.setCursorOn(false);
indexPanel.setFocusable(false);
indexPanel.addLayoutListener(this);
indexPanel.setBackgroundColor(ByteViewerComponentProvider.BG_COLOR);
indexPanel.setBackgroundColorModel(
new ByteViewerBGColorModel(() -> getCurrentComponent().getCursorLocation().getIndex()));
indexedView = new ByteViewerIndexedView(indexPanel);
IndexedScrollPane indexedScrollPane = new IndexedScrollPane(indexedView);
indexedScrollPane.setWheelScrollingEnabled(false);
indexedScrollPane.setColumnHeaderComp(indexedView.getColumnHeader());
indexedScrollPane.setBackground(ByteViewerComponentProvider.BG_COLOR);
statusPanel = createStatusPanel();
add(indexedScrollPane, BorderLayout.CENTER);
add(statusPanel, BorderLayout.SOUTH);
Gui.registerFont(this, ByteViewerComponentProvider.DEFAULT_FONT_ID);
HelpService help = Help.getHelpService();
help.registerHelp(this, new HelpLocation("ByteViewerPlugin", "ByteViewerPlugin"));
}
@@ -713,10 +663,14 @@ public class ByteViewerPanel extends JPanel implements LayoutModel, LayoutListen
@Override
public Layout getLayout(BigInteger index) {
// creates the field layout for the specified index line in the Address column
if (index.compareTo(getNumIndexes()) >= 0) {
return null;
}
Field field = indexFactory.getField(index);
if (field == null) {
int height = indexFactory.getMetrics().getMaxAscent() +
indexFactory.getMetrics().getMaxDescent();
int height = fontMetrics.getMaxAscent() + fontMetrics.getMaxDescent();
field =
new EmptyTextField(height, indexFactory.getStartX(), 0, indexFactory.getWidth());
}
@@ -734,9 +688,7 @@ public class ByteViewerPanel extends JPanel implements LayoutModel, LayoutListen
}
void indexSetChanged() {
for (LayoutModelListener listener : layoutListeners) {
listener.modelSizeChanged(IndexMapper.IDENTITY_MAPPER);
}
layoutListeners.invoke().modelSizeChanged(IndexMapper.IDENTITY_MAPPER);
}
private int getIndexSizeInChars() {
@@ -794,7 +746,7 @@ public class ByteViewerPanel extends JPanel implements LayoutModel, LayoutListen
protected AddressSetView computeVisibleAddresses(List<AnchoredLayout> layouts) {
// Kind of gross, but current component will do
ByteViewerComponent component = getCurrentComponent();
if (component == null || blockSet == null) {
if (component == null || blockSet == null || layouts.isEmpty()) {
return new AddressSet();
}
@@ -835,4 +787,19 @@ public class ByteViewerPanel extends JPanel implements LayoutModel, LayoutListen
public void setViewWidth(String viewName, int width) {
indexedView.setColumnWidth(viewName, width);
}
private void updateFontDependantInfo() {
fontMetrics = getFontMetrics(getFont());
fontHeight = fontMetrics.getHeight();
if (indexFactory != null) {
indexFactory.setFontMetrics(fontMetrics);
}
}
@Override
public void setFont(Font font) {
super.setFont(font);
updateFontDependantInfo();
}
}

View File

@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -24,8 +24,14 @@ import ghidra.program.model.address.AddressSet;
public class EmptyByteBlockSet implements ByteBlockSet {
@Override
public boolean isValid() {
return false;
}
@Override
public void dispose() {
// nothing
}
@Override
@@ -52,6 +58,7 @@ public class EmptyByteBlockSet implements ByteBlockSet {
@Override
public void notifyByteEditing(ByteBlock block, BigInteger index, byte[] oldValue,
byte[] newValue) {
// nothing
}
@Override

View File

@@ -15,7 +15,6 @@
*/
package ghidra.app.plugin.core.byteviewer;
import java.awt.Color;
import java.awt.FontMetrics;
import java.math.BigInteger;
@@ -26,36 +25,41 @@ import ghidra.app.plugin.core.format.*;
import ghidra.program.model.address.AddressOutOfBoundsException;
/**
* Implementation of Field for showing dated formatted according to a
* DataFormatModel.
* Implementation of Field for showing data formatted according to a
* {@link DataFormatModel}.
* <p>
* An instance of this class will be created for each independent position in a byteviewer
* row (typically 16).
*/
class FieldFactory {
private IndexMap indexMap; // maps index to a block and offset into the block
private ByteBlockSet blockSet;
private DataFormatModel model;
private int charWidth; // width in pixels
private final int charWidth; // width in pixels
private int fieldOffset;
private FontMetrics fm;
private int width; // field width
private String noValueStr;
private String readErrorStr; // string to use when there is a read-only exception
private int startX;
private Color editColor;
private Color separatorColor;
private int unitByteSize;
private FieldHighlightFactory highlightFactory;
FieldFactory(DataFormatModel model, int bytesPerLine, int fieldOffset, FontMetrics fm,
ByteViewerHighlighter highlightProvider) {
/**
* Constructor
* @param model data format model that knows how to represent the data
* @param fieldCount number of fields in a row
* @param label label that is used as a renderer in the field viewer
*/
FieldFactory(DataFormatModel model, int bytesPerLine, int fieldOffset, int charWidth,
FontMetrics fm, ByteViewerHighlighter highlightProvider) {
this.model = model;
this.fieldOffset = fieldOffset;
this.fm = fm;
this.highlightFactory = new SimpleHighlightFactory(highlightProvider);
charWidth = fm.charWidth('W');
this.charWidth = charWidth;
width = charWidth * model.getDataUnitSymbolSize();
editColor = ByteViewerComponentProvider.EDITED_TEXT_COLOR;
separatorColor = ByteViewerComponentProvider.SEPARATOR_COLOR;
unitByteSize = model.getUnitByteSize();
}
@@ -98,9 +102,9 @@ class FieldFactory {
ByteBlockInfo info = indexMap.getBlockInfo(index, fieldOffset);
if (info == null) {
if (indexMap.isBlockSeparatorIndex(index)) {
ByteField bf = new ByteField(noValueStr, fm, startX, width, false, fieldOffset,
index, highlightFactory);
bf.setForeground(separatorColor);
ByteField bf = new ByteField(noValueStr, fm, startX, width, charWidth, false,
fieldOffset, index, highlightFactory);
bf.setForeground(ByteViewerComponentProvider.SEPARATOR_COLOR);
return bf;
}
return null;
@@ -116,10 +120,10 @@ class FieldFactory {
return getByteField(readErrorStr, index);
}
String str = model.getDataRepresentation(block, offset);
ByteField bf =
new ByteField(str, fm, startX, width, false, fieldOffset, index, highlightFactory);
ByteField bf = new ByteField(str, fm, startX, width, charWidth, false, fieldOffset,
index, highlightFactory);
if (blockSet.isChanged(block, offset, unitByteSize)) {
bf.setForeground(editColor);
bf.setForeground(ByteViewerComponentProvider.EDITED_TEXT_COLOR);
}
return bf;
}
@@ -150,8 +154,8 @@ class FieldFactory {
void setIndexMap(IndexMap indexMap) {
this.indexMap = indexMap;
if (indexMap != null) {
noValueStr = getString(".");
readErrorStr = getString("?");
noValueStr = ".".repeat(model.getDataUnitSymbolSize());
readErrorStr = "?".repeat(model.getDataUnitSymbolSize());
blockSet = indexMap.getByteBlockSet();
}
else {
@@ -174,32 +178,9 @@ class FieldFactory {
return model.getColumnPosition(block, byteOffset);
}
/**
* Set the color used to denote changes.
* @param c the color
*/
void setEditColor(Color c) {
editColor = c;
}
void setSeparatorColor(Color c) {
separatorColor = c;
}
/**
* Get the padded string that has the given char value.
*/
private String getString(String value) {
StringBuffer sb = new StringBuffer();
int count = model.getDataUnitSymbolSize();
for (int i = 0; i < count; i++) {
sb.append(value);
}
return sb.toString();
}
private ByteField getByteField(String value, BigInteger index) {
return new ByteField(value, fm, startX, width, false, fieldOffset, index, highlightFactory);
return new ByteField(value, fm, startX, width, charWidth, false, fieldOffset, index,
highlightFactory);
}
static class SimpleHighlightFactory implements FieldHighlightFactory {

View File

@@ -15,11 +15,12 @@
*/
package ghidra.app.plugin.core.byteviewer;
import java.math.BigInteger;
import ghidra.app.plugin.core.format.ByteBlock;
import ghidra.app.plugin.core.format.ByteBlockAccessException;
import ghidra.util.*;
import java.math.BigInteger;
import ghidra.util.DataConverter;
import ghidra.util.LittleEndianDataConverter;
/**
* ByteBlock for a byte buffer read from a file.
@@ -44,10 +45,7 @@ class FileByteBlock implements ByteBlock {
@Override
public String getLocationRepresentation(BigInteger bigIndex) {
int index = bigIndex.intValue();
if (index < buf.length) {
return pad(Integer.toString(index), 8);
}
return null;
return index < buf.length ? "%08d".formatted(index) : null;
}
@Override
@@ -83,6 +81,18 @@ class FileByteBlock implements ByteBlock {
return 0;
}
@Override
public int getBytes(byte[] bytes, BigInteger bigIndex, int count)
throws ByteBlockAccessException {
int index = bigIndex.intValue();
if (index < buf.length) {
count = Math.min(count, buf.length - index);
System.arraycopy(buf, index, bytes, 0, count);
return count;
}
return 0;
}
/* (non-Javadoc)
* @see ghidra.app.plugin.core.format.ByteBlock#getShort(int)
*/
@@ -214,14 +224,4 @@ class FileByteBlock implements ByteBlock {
byte[] getBytes() {
return buf;
}
private String pad(String str, int length) {
StringBuffer sb = new StringBuffer();
int nspaces = length - str.length();
for (int i = 0; i < nspaces; i++) {
sb.append(" ");
}
sb.append(str);
return sb.toString();
}
}

View File

@@ -38,7 +38,7 @@ class IndexFieldFactory {
private int charWidth;
private String noValueStr;
private int startX;
private Color missingValueColor;
private Color missingValueColor = ByteViewerComponentProvider.SEPARATOR_COLOR;
private FieldHighlightFactory highlightFactory = new DummyHighlightFactory();
/**
@@ -46,15 +46,8 @@ class IndexFieldFactory {
* @param metrics the FontMetrics this field should use to do size computations
*/
IndexFieldFactory(FontMetrics metrics) {
this.metrics = metrics;
charWidth = metrics.charWidth('W');
width = ByteViewerComponentProvider.DEFAULT_NUMBER_OF_CHARS * charWidth;
missingValueColor = ByteViewerComponentProvider.SEPARATOR_COLOR;
startX = charWidth;
// initialize to a non-null value
setSize(1);
setFontMetrics(metrics);
setSize(ByteViewerComponentProvider.DEFAULT_NUMBER_OF_CHARS);
}
/**
@@ -95,18 +88,6 @@ class IndexFieldFactory {
return new SimpleTextField(locRep, metrics, startX, width, false, highlightFactory);
}
public FontMetrics getMetrics() {
return metrics;
}
/**
* Sets the starting x position for the fields generated by this factory
* @param x The
*/
public void setStartX(int x) {
startX = x;
}
/**
* Returns the starting x position for the fields generated by this factory.
* @return the starting x position for the fields generated by this factory.
@@ -143,8 +124,12 @@ class IndexFieldFactory {
noValueStr = StringUtils.repeat('.', charCount);
}
void setMissingValueColor(Color c) {
missingValueColor = c;
void setFontMetrics(FontMetrics fm) {
this.metrics = fm;
// 'W' is a good exemplar because addresses and address space names will typically be latin
charWidth = metrics.charWidth('W');
startX = charWidth;
}
static class DummyHighlightFactory implements FieldHighlightFactory {

View File

@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -38,20 +38,13 @@ public class InteractivePanelManager {
public InteractivePanelManager() {
JTable table = new JTable();
header = table.getTableHeader();
header.setFont(ByteViewerComponentProvider.HEADER_FONT);
columnModel = header.getColumnModel();
separatorWidth = (new JSeparator(SwingConstants.VERTICAL)).getPreferredSize().width;
mainPanel = new JPanel(new HeaderLayoutManager());
columnModel.addColumnModelListener(new PanelManagerColumnModelListener());
}
/**
* Sets the font for the header component.
* @param font the font to be used to display view names in the header
*/
public void setHeaderFont(Font font) {
header.setFont(font);
}
/**
* Adds a component and it's name to the set of managed components.
* @param name the name to display in its column header.
@@ -297,7 +290,9 @@ public class InteractivePanelManager {
record ComponentData(String name, JComponent component) {
@Override
public String toString() {
return name;
return component instanceof ByteViewerComponentNamer bvcn
? bvcn.getByteViewerComponentName()
: name;
}
}

View File

@@ -19,7 +19,6 @@ import java.math.BigInteger;
import ghidra.app.plugin.core.format.ByteBlock;
import ghidra.app.plugin.core.format.ByteBlockAccessException;
import ghidra.program.database.mem.ByteMappingScheme;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.*;
import ghidra.program.model.mem.*;
@@ -33,7 +32,6 @@ public class MemoryByteBlock implements ByteBlock {
private Memory memory;
private Address start;
private boolean bigEndian;
private Address mAddr;
private Program program;
/**
@@ -47,9 +45,8 @@ public class MemoryByteBlock implements ByteBlock {
this.program = program;
this.memory = memory;
this.block = block;
start = block.getStart();
bigEndian = memory.isBigEndian();
mAddr = start;
this.start = block.getStart();
this.bigEndian = memory.isBigEndian();
}
/**
@@ -132,6 +129,17 @@ public class MemoryByteBlock implements ByteBlock {
}
}
@Override
public int getBytes(byte[] bytes, BigInteger index, int count) throws ByteBlockAccessException {
try {
Address addr = getAddress(index);
return memory.getBytes(addr, bytes, 0, count);
}
catch (MemoryAccessException e) {
throw new ByteBlockAccessException(e.getMessage());
}
}
@Override
public boolean hasValue(BigInteger index) {
Address addr = getAddress(index);
@@ -293,37 +301,22 @@ public class MemoryByteBlock implements ByteBlock {
return (int) (start.getOffset() % radix);
}
private Address getMappedAddress(Address addr) {
MemoryBlock memBlock = memory.getBlock(addr);
if (memBlock != null && memBlock.getType() == MemoryBlockType.BYTE_MAPPED) {
try {
MemoryBlockSourceInfo info = memBlock.getSourceInfos().get(0);
AddressRange mappedRange = info.getMappedRange().get();
ByteMappingScheme byteMappingScheme = info.getByteMappingScheme().get();
addr = byteMappingScheme.getMappedSourceAddress(mappedRange.getMinAddress(),
addr.subtract(memBlock.getStart()));
}
catch (AddressOverflowException e) {
// ignore
}
}
return addr;
}
/**
* Get the address based on the index.
*/
public Address getAddress(BigInteger index) {
try {
mAddr = start;
mAddr = mAddr.addNoWrap(index);
return mAddr;
return start.addNoWrap(index);
}
catch (AddressOverflowException e) {
throw new IndexOutOfBoundsException("Index " + index + " is not in this block");
}
}
public BigInteger getIndex(Address addr) {
return addr.getOffsetAsBigInteger().subtract(start.getOffsetAsBigInteger());
}
/**
* Indicates whether some other object is "equal to" this one.
*/

View File

@@ -1,47 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.byteviewer;
import javax.swing.Icon;
import docking.ActionContext;
import docking.action.DockingAction;
import docking.action.ToolBarData;
import generic.theme.GIcon;
import ghidra.framework.plugintool.Plugin;
import ghidra.framework.plugintool.PluginTool;
class OptionsAction extends DockingAction {
public static final Icon OPTIONS_ICON = new GIcon("icon.plugin.byteviewer.options");
private final ByteViewerComponentProvider provider;
private final PluginTool tool;
public OptionsAction(ByteViewerComponentProvider provider, Plugin plugin) {
super("Byte Viewer Options", plugin.getName());
this.provider = provider;
this.tool = plugin.getTool();
setEnabled(false);
setDescription("Set Byte Viewer Options");
setToolBarData(new ToolBarData(OPTIONS_ICON, "ZSettings"));
}
@Override
public void actionPerformed(ActionContext context) {
tool.showDialog(new ByteViewerOptionsDialog(provider), provider);
}
}

View File

@@ -28,6 +28,7 @@ import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection;
import ghidra.util.NumericUtilities;
/**
* ByteBlockSet implementation for a Program object.
@@ -238,12 +239,7 @@ public class ProgramByteBlockSet implements ByteBlockSet {
try {
long off = address.subtract(memBlocks[i].getStart());
BigInteger offset =
(off < 0)
? BigInteger.valueOf(off + 0x8000000000000000L)
.subtract(
BigInteger.valueOf(0x8000000000000000L))
: BigInteger.valueOf(off);
BigInteger offset = NumericUtilities.unsignedLongToBigInteger(off);
return new ByteBlockInfo(blocks[i], offset);
}
catch (Exception e) {

View File

@@ -27,7 +27,7 @@ import javax.swing.*;
import docking.ActionContext;
import docking.DockingUtils;
import docking.action.*;
import docking.action.builder.ActionBuilder;
import docking.widgets.fieldpanel.support.ViewerPosition;
import generic.theme.GIcon;
import ghidra.app.events.*;
@@ -59,8 +59,6 @@ public class ProgramByteViewerComponentProvider extends ByteViewerComponentProvi
private WeakSet<NavigatableRemovalListener> navigationListeners =
WeakDataStructureFactory.createCopyOnWriteWeakSet();
private CloneByteViewerAction cloneByteViewerAction;
protected Program program;
protected ProgramSelection currentSelection;
protected ProgramSelection liveSelection;
@@ -105,8 +103,16 @@ public class ProgramByteViewerComponentProvider extends ByteViewerComponentProvi
}
public void createProgramActions() {
cloneByteViewerAction = new CloneByteViewerAction();
tool.addLocalAction(this, cloneByteViewerAction);
new ActionBuilder("ByteViewer Clone", plugin.getName())
.toolBarIcon(new GIcon("icon.provider.clone"))
.toolBarGroup("ZZZ")
.description("Create a snapshot (disconnected) copy of this Bytes window ")
.helpLocation(new HelpLocation("Snapshots", "Snapshots_Start"))
.keyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_T,
DockingUtils.CONTROL_KEY_MODIFIER_MASK | InputEvent.SHIFT_DOWN_MASK))
.enabledWhen(ac -> blockSet != null && blockSet.isValid())
.onAction(ac -> cloneWindow())
.buildAndInstallLocal(this);
}
@Override
@@ -150,7 +156,7 @@ public class ProgramByteViewerComponentProvider extends ByteViewerComponentProvi
}
protected ByteViewerActionContext newByteViewerActionContext() {
return new ByteViewerActionContext(this);
return new ByteViewerActionContext(this, panel.getCurrentComponent());
}
@Override
@@ -233,9 +239,6 @@ public class ProgramByteViewerComponentProvider extends ByteViewerComponentProvi
}
protected void doSetProgram(Program newProgram) {
setOptionsAction.setEnabled(newProgram != null);
cloneByteViewerAction.setEnabled(newProgram != null);
if (program != null) {
program.removeListener(this);
}
@@ -244,8 +247,8 @@ public class ProgramByteViewerComponentProvider extends ByteViewerComponentProvi
clipboardProvider.setProgram(newProgram);
for (ByteViewerComponent byteViewerComponent : viewMap.values()) {
DataFormatModel dataModel = byteViewerComponent.getDataModel();
if (dataModel instanceof ProgramDataFormatModel) {
((ProgramDataFormatModel) dataModel).setProgram(newProgram);
if (dataModel instanceof ProgramDataFormatModel pdfm) {
pdfm.setProgram(newProgram);
}
}
@@ -254,6 +257,7 @@ public class ProgramByteViewerComponentProvider extends ByteViewerComponentProvi
}
setByteBlocks(null);
updateTitle();
contextChanged();
}
protected void updateTitle() {
@@ -615,9 +619,12 @@ public class ProgramByteViewerComponentProvider extends ByteViewerComponentProvi
@Override
protected void updateLiveSelection(ByteBlockSelection selection) {
AbstractSelectionPluginEvent event = blockSet.getPluginEvent(plugin.getName(), selection);
liveSelection = event.getSelection();
updateTitle();
if (blockSet != null) {
AbstractSelectionPluginEvent event =
blockSet.getPluginEvent(plugin.getName(), selection);
liveSelection = event.getSelection();
updateTitle();
}
}
@Override
@@ -760,29 +767,6 @@ public class ProgramByteViewerComponentProvider extends ByteViewerComponentProvi
newProvider.panel.setViewerPosition(viewerPosition);
}
//==================================================================================================
// Inner Classes
//==================================================================================================
private class CloneByteViewerAction extends DockingAction {
public CloneByteViewerAction() {
super("ByteViewer Clone", plugin.getName());
Icon image = new GIcon("icon.provider.clone");
setToolBarData(new ToolBarData(image, "ZZZ"));
setDescription("Create a snapshot (disconnected) copy of this Bytes window ");
setHelpLocation(new HelpLocation("Snapshots", "Snapshots_Start"));
setKeyBindingData(new KeyBindingData(KeyEvent.VK_T,
DockingUtils.CONTROL_KEY_MODIFIER_MASK | InputEvent.SHIFT_DOWN_MASK));
}
@Override
public void actionPerformed(ActionContext context) {
cloneWindow();
}
}
@Override
public void addNavigatableListener(NavigatableRemovalListener listener) {
navigationListeners.add(listener);

View File

@@ -1,47 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.byteviewer;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import docking.ActionContext;
import docking.DockingUtils;
import docking.action.*;
import generic.theme.GIcon;
import ghidra.framework.plugintool.Plugin;
class ToggleEditAction extends ToggleDockingAction {
private final ByteViewerComponentProvider provider;
public ToggleEditAction(ByteViewerComponentProvider provider, Plugin plugin) {
super("Enable/Disable Byteviewer Editing", plugin.getName());
this.provider = provider;
setToolBarData(new ToolBarData(new GIcon("icon.base.edit.bytes"), "Byteviewer"));
setKeyBindingData(new KeyBindingData(
KeyEvent.VK_E, DockingUtils.CONTROL_KEY_MODIFIER_MASK | InputEvent.ALT_DOWN_MASK));
setDescription("Enable/Disable editing of bytes in Byte Viewer panels.");
setSelected(false);
setEnabled(true);
}
@Override
public void actionPerformed(ActionContext context) {
boolean isSelected = isSelected();
provider.setEditMode(isSelected);
}
}

View File

@@ -15,6 +15,8 @@
*/
package ghidra.app.plugin.core.format;
import java.math.BigInteger;
import ghidra.app.plugin.core.byteviewer.MemoryByteBlock;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.Listing;
@@ -23,8 +25,6 @@ import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.util.HelpLocation;
import java.math.BigInteger;
/**
* Converts byte values to Ascii representation.
*/
@@ -177,31 +177,6 @@ public class AddressFormatModel implements ProgramDataFormatModel {
return true;
}
/**
* Returns true if the formatter allows values to be changed.
*/
@Override
public boolean isEditable() {
return false;
}
/**
* Overwrite a value in a ByteBlock.
* @param block block to change
* @param index byte index into the block
* @param pos The position within the unit where c will be the
* new character.
* @param c new character to put at pos param
* @return true if the replacement is legal, false if the
* replacement value would not make sense for this format, e.g.
* attempt to put a 'z' in a hex unit.
* block
*/
@Override
public boolean replaceValue(ByteBlock block, BigInteger index, int charPosition, char c) {
return false;
}
/**
* Get the number of characters separating units.
*/
@@ -210,34 +185,6 @@ public class AddressFormatModel implements ProgramDataFormatModel {
return 0;
}
/**
* Get number of units in a group. A group may represent
* multiple units shown as one entity. This format does not
* support groups.
*/
@Override
public int getGroupSize() {
return 0;
}
/**
* Set the number of units in a group. This format does not
* support groups.
* @throws UnsupportedOperationException
*/
@Override
public void setGroupSize(int groupSize) {
throw new UnsupportedOperationException("groups are not supported");
}
/**
* @see ghidra.app.plugin.core.format.DataFormatModel#validateBytesPerLine(int)
*/
@Override
public boolean validateBytesPerLine(int bytesPerLine) {
return true;
}
/**
* Set the program. This formatter is dependent upon listing from program.
* There are two cases where this depedency only appears. All Formatters that

View File

@@ -1,197 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.format;
import ghidra.app.plugin.core.byteviewer.ByteViewerComponentProvider;
import ghidra.util.HelpLocation;
import java.math.BigInteger;
/**
* Converts byte values to Ascii representation.
*/
public class AsciiFormatModel implements UniversalDataFormatModel {
private int symbolSize;
public AsciiFormatModel() {
symbolSize = 1;
}
/**
* Get the name of this formatter.
*/
@Override
public String getName() {
return "Ascii";
}
/**
* Get the number of bytes to make a unit; in this case it
* takes 1 byte to make an Ascii value.
*/
@Override
public int getUnitByteSize() {
return 1;
}
/**
* Given a character position from 0 to data unit symbol size - 1
* it returns a number from 0 to unit byte size - 1 indicating which
* byte the character position was obtained from.
*/
@Override
public int getByteOffset(ByteBlock block, int position) {
return 0;
}
/**
* Given the byte offset into a unit, get the column position.
*/
@Override
public int getColumnPosition(ByteBlock block, int byteOffset) {
return 0;
}
/**
* Gets the number of characters required to display a
* unit.
*/
@Override
public int getDataUnitSymbolSize() {
return symbolSize;
}
/**
* Gets the string representation at the given index in the block.
* @param block block to change
* @param index byte index into the block
* @throws ByteBlockAccessException if the block cannot be read
* @throws IndexOutOfBoundsException if index is not valid for the
* block
*/
@Override
public String getDataRepresentation(ByteBlock block, BigInteger index)
throws ByteBlockAccessException {
byte b = block.getByte(index);
String str = null;
if (b < 0x20 || b == 0x7f) {
str = ".";
}
else {
char[] charArray = { (char) b };
str = new String(charArray);
}
return (str);
}
/**
* Returns true if the formatter allows values to be changed.
*/
@Override
public boolean isEditable() {
return true;
}
/**
* Overwrite a value in a ByteBlock.
* @param block block to change
* @param index byte index into the block
* @param pos The position within the unit where c will be the
* new character.
* @param c new character to put at pos param
* @return true if the replacement is legal, false if the
* replacement value would not make sense for this format, e.g.
* attempt to put a 'z' in a hex unit.
* @throws ByteBlockAccessException if the block cannot be updated
* @throws IndexOutOfBoundsException if index is not valid for the
* block
*/
@Override
public boolean replaceValue(ByteBlock block, BigInteger index, int charPosition, char c)
throws ByteBlockAccessException {
if (charPosition != 0) {
return false;
}
block.getByte(index);
byte cb = (byte) c;
if (cb < 0x20 || cb == 0x7f) {
return false;
}
block.setByte(index, cb);
return true;
}
/**
* Get number of units in a group. A group may represent
* multiple units shown as one entity. This format does not
* support groups.
* @throws UnsupportedOperationException
*/
@Override
public int getGroupSize() {
return 0;
}
/**
* Set the number of units in a group. This format does not
* support groups.
* @throws UnsupportedOperationException
*/
@Override
public void setGroupSize(int groupSize) {
throw new UnsupportedOperationException("groups are not supported");
}
/**
* Get the number of characters separating units.
*/
@Override
public int getUnitDelimiterSize() {
return 0;
}
/**
* @see ghidra.app.plugin.core.format.DataFormatModel#validateBytesPerLine(int)
*/
@Override
public boolean validateBytesPerLine(int bytesPerLine) {
return true;
}
/* (non-Javadoc)
* @see ghidra.app.plugin.format.DataFormatModel#getHelpLocation()
*/
@Override
public HelpLocation getHelpLocation() {
return new HelpLocation("ByteViewerPlugin", "Ascii");
}
@Override
public void dispose() {
}
public boolean supportsProvider(ByteViewerComponentProvider provider) {
return true;
}
}

View File

@@ -15,14 +15,14 @@
*/
package ghidra.app.plugin.core.format;
import ghidra.util.HelpLocation;
import java.math.BigInteger;
import ghidra.util.HelpLocation;
/**
* Converts byte values to binary representation.
*/
public class BinaryFormatModel implements UniversalDataFormatModel {
public class BinaryFormatModel implements UniversalDataFormatModel, MutableDataFormatModel {
private int symbolSize;
private static final String GOOD_CHARS = "01";
@@ -99,18 +99,10 @@ public class BinaryFormatModel implements UniversalDataFormatModel {
str = str.substring(str.length() - symbolSize);
}
return pad(str);
return DataFormatModel.pad(str, symbolSize);
}
/**
* Returns true to allow values to be changed.
*/
@Override
public boolean isEditable() {
return (true);
}
/**
* Overwrite a value in a ByteBlock.
* @param block block to change
@@ -154,26 +146,6 @@ public class BinaryFormatModel implements UniversalDataFormatModel {
return true;
}
/**
* Get number of units in a group. A group may represent
* multiple units shown as one entity. This format does not
* support groups.
*/
@Override
public int getGroupSize() {
return 1;
}
/**
* Set the number of units in a group. This format does not
* support groups.
* @throws UnsupportedOperationException
*/
@Override
public void setGroupSize(int groupSize) {
throw new UnsupportedOperationException("groups are not supported");
}
/**
* Get the number of characters separating units.
*/
@@ -182,38 +154,8 @@ public class BinaryFormatModel implements UniversalDataFormatModel {
return 1;
}
/**
* @see ghidra.app.plugin.core.format.DataFormatModel#validateBytesPerLine(int)
*/
@Override
public boolean validateBytesPerLine(int bytesPerLine) {
return true;
}
/////////////////////////////////////////////////////////////////
// *** private methods ***
/////////////////////////////////////////////////////////////////
private String pad(String value) {
StringBuffer sb = new StringBuffer();
int len = symbolSize - value.length();
for (int i = 0; i < len; i++) {
sb.append("0");
}
sb.append(value);
return (sb.toString());
}
/* (non-Javadoc)
* @see ghidra.app.plugin.format.DataFormatModel#getHelpLocation()
*/
@Override
public HelpLocation getHelpLocation() {
return new HelpLocation("ByteViewerPlugin", "Binary");
}
@Override
public void dispose() {
// nothing to do
}
}

View File

@@ -51,6 +51,17 @@ public interface ByteBlock {
*/
public byte getByte(BigInteger index) throws ByteBlockAccessException;
/**
* Get bytes from given index.
*
* @param bytes destination
* @param index byte index
* @param count number of bytes to get
* @return actual number of bytes copied into destination
* @throws ByteBlockAccessException if error
*/
public int getBytes(byte[] bytes, BigInteger index, int count) throws ByteBlockAccessException;
/**
* Returns true if this ByteBlock has byte values at the specified index.
*

View File

@@ -25,6 +25,14 @@ import ghidra.program.model.address.AddressSet;
* Interface to define methods for getting byte blocks and translating events.
*/
public interface ByteBlockSet {
/**
* {@return true if this instance represents a valid source of data, false if this
* instance does not represent a valid source of data}
*/
default public boolean isValid() {
return true;
}
/**
* Get the blocks in this set.

View File

@@ -0,0 +1,236 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.format;
import java.awt.FontMetrics;
import java.lang.Character.UnicodeScript;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.List;
import docking.ActionContext;
import docking.Tool;
import docking.action.DockingActionIf;
import docking.action.builder.ToggleActionBuilder;
import docking.actions.PopupActionProvider;
import ghidra.app.plugin.core.byteviewer.*;
import ghidra.program.model.lang.Endian;
import ghidra.util.*;
import ghidra.util.charset.CharsetInfo;
import ghidra.util.charset.CharsetInfoManager;
/**
* Converts byte values to Character representation. (previously the AsciiFormatModel)
*/
public class CharacterFormatModel implements UniversalDataFormatModel, MutableDataFormatModel,
PopupActionProvider, CursorWidthDataFormatModel, TooltipDataFormatModel {
public final static String NAME = "Chars";
private CharsetInfo csi = CharsetInfoManager.getInstance().get(StandardCharsets.US_ASCII);
private Charset cs = StandardCharsets.US_ASCII;
private int maxBytesPerChar = 1;
private boolean compactChars = true;
private int bytesPerChar = 1;
public CharacterFormatModel() {
}
@Override
public void setByteViewerConfigOptions(ByteViewerConfigOptions options) {
this.csi = options.getCharsetInfo();
this.cs = csi.getCharset();
this.maxBytesPerChar = Math.max(csi.getMaxBytesPerChar(), 1);
this.compactChars = options.isCompactChars();
this.bytesPerChar =
options.isUseCharAlignment() && csi.getAlignment() > 1 ? csi.getAlignment() : 1;
}
@Override
public int getCursorWidth(FontMetrics fm) {
return fm.charWidth('W') * (compactChars ? 1 : 2);
}
@Override
public String getName() {
return NAME;
}
@Override
public String getDescriptiveName() {
return NAME + " (%s)".formatted(cs.name());
}
@Override
public int getUnitByteSize() {
return bytesPerChar;
}
@Override
public int getByteOffset(ByteBlock block, int position) {
return 0;
}
@Override
public int getColumnPosition(ByteBlock block, int byteOffset) {
return 0;
}
@Override
public int getDataUnitSymbolSize() {
return 1;
}
@Override
public String getDataRepresentation(ByteBlock block, BigInteger index)
throws ByteBlockAccessException {
Integer codePoint = getCodePointAt(block, index);
if (codePoint == null) {
return "?";
}
int cp = codePoint.intValue();
if (cp == StringUtilities.UNICODE_REPLACEMENT || Character.isISOControl(cp) ||
!Character.isValidCodePoint(cp)) {
return ".";
}
return Character.toString(cp);
}
private Integer getCodePointAt(ByteBlock block, BigInteger index)
throws ByteBlockAccessException {
// create buffer with as many bytes as it may take to read the largest
// encoded character using the current charset.
// This may give us more than 1 character when decoded, we throw away anything
// other than the first char.
// Future: would be nice to not have to call getAdjustedCS() for each fetch operation
Charset bomCS = getAdjustedCS(block);
byte[] bytes = new byte[maxBytesPerChar];
int byteCount = block.getBytes(bytes, index, maxBytesPerChar);
String s = new String(bytes, 0, byteCount, bomCS);
if (s.isEmpty()) {
return null;
}
return s.codePointAt(0);
}
private Charset getAdjustedCS(ByteBlock block) {
// returns an alternate charset that fits the endianness of the memory
// to avoid spurious BOM bytes being emitted and incorrect
// assumption about how to decode bytes
if (CharsetInfoManager.isBOMCharset(csi.getName())) {
Endian endian = block.isBigEndian() ? Endian.BIG : Endian.LITTLE;
CharsetInfo bomCSI =
CharsetInfoManager.getInstance().get(csi.getName() + endian.toShortString());
return bomCSI != null ? bomCSI.getCharset() : cs;
}
return cs;
}
private byte[] getBytesForCodePoint(int cp, Charset bomCS) {
String s = Character.toString(cp);
if (bomCS.canEncode() && bomCS.newEncoder().canEncode(s)) {
ByteBuffer bb = bomCS.encode(s);
byte[] bytes = new byte[bb.limit()];
bb.get(bytes);
return bytes;
}
return null;
}
@Override
public boolean replaceValue(ByteBlock block, BigInteger index, int charPosition, char c)
throws ByteBlockAccessException {
if (charPosition != 0) {
return false;
}
block.getByte(index);
byte cb = (byte) c;
// right now only supports US-ASCII when replacing values
if (cb < 0x20 || cb == 0x7f) {
return false;
}
block.setByte(index, cb);
return true;
}
@Override
public int getUnitDelimiterSize() {
return 0;
}
@Override
public HelpLocation getHelpLocation() {
return new HelpLocation("ByteViewerPlugin", "Chars");
}
@Override
public List<DockingActionIf> getPopupActions(Tool tool, ActionContext context) {
return List.of(new ToggleActionBuilder("CompactCharWidth", "ByteViewerPlugin")
.selected(compactChars)
.withContext(ByteViewerActionContext.class)
.popupMenuPath("Compact/Wide Layout")
.onAction(ac -> ac.getComponentProvider().setCompactChars(!compactChars))
.helpLocation(new HelpLocation("ByteViewerPlugin", "CompactCharWidth"))
.build());
}
@Override
public String getTooltip(ByteBlock block, BigInteger index, ByteViewerComponent comp) {
try {
Integer cp;
if ((cp = getCodePointAt(block, index)) != null) {
Charset bomCS = getAdjustedCS(block);
byte[] bytes = getBytesForCodePoint(cp, bomCS);
boolean canDisplay = comp.getFont().canDisplay(cp);
UnicodeScript script = UnicodeScript.of(cp);
String charRep =
canDisplay && !Character.isISOControl(cp) ? Character.toString(cp) : "NA";
String bytesRep = bytes != null
? NumericUtilities.convertBytesToString(bytes, " ")
: "unavailable";
String s = """
<html>
<b>Character info:</b><br>
<table>
<tr><td>Char</td><td>Unicode</td><td>Script</td></tr>
<tr><b>%s</b></td><td>0x%04x</td><td>%s</td></tr>
</table>
<hr>
<br>
%s Bytes: <b>%s</b><br>
%s
<br>
""".formatted(charRep, cp, script, bomCS.name(), bytesRep,
!canDisplay ? "<br>(unrenderable)" : "");
return s;
}
}
catch (ByteBlockAccessException e) {
// ignore
}
return null;
}
}

View File

@@ -0,0 +1,26 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.format;
import java.awt.FontMetrics;
/**
* Optional interface for DataFormatModels that allows the model to specify the width of individual
* characters and the cursor needed to display those characters
*/
public interface CursorWidthDataFormatModel extends DataFormatModel {
int getCursorWidth(FontMetrics fm);
}

View File

@@ -1,13 +1,12 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -16,11 +15,12 @@
*/
package ghidra.app.plugin.core.format;
import java.math.BigInteger;
import ghidra.app.plugin.core.byteviewer.ByteViewerConfigOptions;
import ghidra.util.HelpLocation;
import ghidra.util.classfinder.ExtensionPoint;
import java.math.BigInteger;
/**
* NOTE: ALL DataFormatModel CLASSES MUST END IN "FormatModel". If not,
* the ClassSearcher will not find them.
@@ -30,24 +30,28 @@ import java.math.BigInteger;
*/
public interface DataFormatModel extends ExtensionPoint {
public static final int NEXT_UNIT = -1;
public static final int PREVIOUS_UNIT = -1;
/**
* Gets the number of bytes to make a unit, e.g.,
* for 'byte' unit size =1, for 'unicode' unit size = 2, etc.
*/
public int getUnitByteSize();
int getUnitByteSize();
/**
* Gets data format name.
*/
public String getName();
String getName();
/**
* {@return a descriptive name for this data format, used for labels / headers}
*/
default String getDescriptiveName() {
return getName();
}
/**
* Gets the help location for this format
*/
public HelpLocation getHelpLocation();
HelpLocation getHelpLocation();
/**
* Gets the number of characters required to display a
@@ -55,19 +59,19 @@ public interface DataFormatModel extends ExtensionPoint {
* may display a unit as '0xff'. The data unit
* size returned would be 4.
*/
public int getDataUnitSymbolSize();
int getDataUnitSymbolSize();
/**
* Given a character position from 0 to data unit symbol size - 1
* it returns a number from 0 to unit byte size - 1 indicating which
* byte the character position was obtained from.
*/
public int getByteOffset(ByteBlock block, int position);
int getByteOffset(ByteBlock block, int position);
/**
* Given the byte offset into a unit, get the column position.
*/
public int getColumnPosition(ByteBlock block, int byteOffset);
int getColumnPosition(ByteBlock block, int byteOffset);
/**
* Gets the string representation at the given index in the block.
@@ -77,56 +81,37 @@ public interface DataFormatModel extends ExtensionPoint {
* @throws IndexOutOfBoundsException if index is not valid for the
* block
*/
public String getDataRepresentation(ByteBlock block, BigInteger index)
throws ByteBlockAccessException;
String getDataRepresentation(ByteBlock block, BigInteger index) throws ByteBlockAccessException;
default void setByteViewerConfigOptions(ByteViewerConfigOptions options) {
// default do-nothing
}
/**
* Returns true if the formatter allows values to be changed.
* Returns an error message string if the supplied {@link ByteViewerConfigOptions} are
* problematic, otherwise returns null.
*
* @param candidateOptions {@link ByteViewerConfigOptions}
* @return null if candidate config options are ok, otherwise error message string
*/
public boolean isEditable();
/**
* Overwrite a value in a ByteBlock.
* @param block block to change
* @param index byte index into the block
* @param pos The position within the unit where c will be the
* new character.
* @param c new character to put at pos param
* @return true if the replacement is legal, false if the
* replacement value would not make sense for this format, e.g.
* attempt to put a 'z' in a hex unit.
* @throws ByteBlockAccessException if the block cannot be updated
* @throws IndexOutOfBoundsException if index is not valid for the
* block
*/
public boolean replaceValue(ByteBlock block, BigInteger index, int pos, char c)
throws ByteBlockAccessException;
/**
* Get number of units in a group. A group may represent
* multiple units shown as one entity.
*/
public int getGroupSize();
/**
* Set the number of units in a group.
* @throws UnsupportedOperationException if model does not
* support groups
*/
public void setGroupSize(int groupSize);
default String validateByteViewerConfigOptions(ByteViewerConfigOptions candidateOptions) {
return null;
}
/**
* Get the number of characters separating units.
*/
public int getUnitDelimiterSize();
int getUnitDelimiterSize();
/**
* Verify that this model can support the given bytes per line
* value.
* @return true if this model supports the given number of bytes per line
*/
public boolean validateBytesPerLine(int bytesPerLine);
default void dispose() {
// do nothing by default
}
public void dispose();
static String pad(String value, int symbolSize) {
return pad(value, symbolSize, "0");
}
static String pad(String value, int symbolSize, String padChar) {
return padChar.repeat(Math.max(symbolSize - value.length(), 0)) + value;
}
}

View File

@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -127,65 +127,6 @@ public class DisassembledFormatModel implements ProgramDataFormatModel {
return str;
}
/**
* Returns true if the formatter allows values to be changed.
*/
@Override
public boolean isEditable() {
return false;
}
/**
* Overwrite a value in a ByteBlock.
* @param block block to change
* @param index byte index into the block
* @param pos The position within the unit where c will be the
* new character.
* @param c new character to put at pos param
* @return true if the replacement is legal, false if the
* replacement value would not make sense for this format, e.g.
* attempt to put a 'z' in a hex unit.
* @throws ByteBlockAccessException if the block cannot be updated
* @throws IndexOutOfBoundsException if index is not valid for the
* block
*/
@Override
public boolean replaceValue(ByteBlock block, BigInteger index, int charPosition, char c)
throws ByteBlockAccessException {
if (charPosition != 0) {
return false;
}
byte cb = (byte) c;
if (cb < 0x20 || cb == 0x7f) {
return (false);
}
block.setByte(index, cb);
return true;
}
/**
* Get number of units in a group. A group may represent
* multiple units shown as one entity. This format does not
* support groups.
*/
@Override
public int getGroupSize() {
return 0;
}
/**
* Set the number of units in a group. This format does not
* support groups.
* @throws UnsupportedOperationException
*/
@Override
public void setGroupSize(int groupSize) {
throw new UnsupportedOperationException("groups are not supported");
}
/**
* Get the number of characters separating units.
@@ -195,14 +136,6 @@ public class DisassembledFormatModel implements ProgramDataFormatModel {
return 0; // no space between units
}
/**
* @see ghidra.app.plugin.core.format.DataFormatModel#validateBytesPerLine(int)
*/
@Override
public boolean validateBytesPerLine(int bytesPerLine) {
return true;
}
/**
* Set the program. This formatter is dependent upon listing from program.
* There are two cases where this dependency only appears. All Formatters that

View File

@@ -15,252 +15,97 @@
*/
package ghidra.app.plugin.core.format;
import ghidra.util.HelpLocation;
import java.math.BigInteger;
import ghidra.app.plugin.core.byteviewer.ByteViewerConfigOptions;
import ghidra.util.HelpLocation;
/**
* Converts byte values to hex representation.
* Converts byte values to hex representation, optionally grouping the byte values
* together in groups of {@link #getHexGroupSize()}.
*/
public class HexFormatModel implements UniversalDataFormatModel {
public class HexFormatModel implements UniversalDataFormatModel, MutableDataFormatModel {
public final static String NAME = "Hex";
private int symbolSize;
private int unitByteSize;
private boolean prefixEnabled;
private boolean alphaCapsEnabled;
private int symbolSize = 2;
private int groupSize = 1;
private String fullSymbolErrorStr = "??";
private static final String GOOD_CHARS = "0123456789abcdefABCDEF";
public HexFormatModel() {
this.prefixEnabled = false;
this.alphaCapsEnabled = false;
unitByteSize = groupSize;
if (prefixEnabled) {
symbolSize = 4 * groupSize; // there are 2 chars per byte of data
}
else {
symbolSize = 2 * groupSize; // there are 2 chars per byte of data
}
}
/**
* Get the name of this formatter.
*/
@Override
public String getName() {
return NAME;
}
/**
* Get the number of bytes to make a unit; in this case,
* returns 1.
*/
@Override
public int getUnitByteSize() {
return unitByteSize;
return groupSize;
}
/**
* Gets the number of characters required to display a
* unit.
* @return 2 for number of characters in a unit
*/
@Override
public int getDataUnitSymbolSize() {
return symbolSize;
}
/**
* Get number of units in a group.
*/
@Override
public int getGroupSize() {
public void setByteViewerConfigOptions(ByteViewerConfigOptions options) {
groupSize = options.getHexGroupSize();
symbolSize = 2 * groupSize;
fullSymbolErrorStr = "??".repeat(groupSize);
}
@Override
public String validateByteViewerConfigOptions(ByteViewerConfigOptions candidateOptions) {
// we can't rely on the caller to pre-check our validity based on
// groupsize % bytes_per_line because our groupsize will change after the configoptions
// are set.
if (candidateOptions.getBytesPerLine() % candidateOptions.getHexGroupSize() != 0) {
return "Hex (%d bytes) is not a multiple of %d".formatted(
candidateOptions.getHexGroupSize(), candidateOptions.getBytesPerLine());
}
return null;
}
public int getHexGroupSize() {
return groupSize;
}
/**
* Set the number of units in a group.
*/
@Override
public void setGroupSize(int groupSize) {
this.groupSize = groupSize;
unitByteSize = groupSize;
symbolSize = (2 * groupSize);
}
/**
* Should this model display spaces by groupSize?
*/
public boolean isSpaceByGroupSize() {
return false;
}
/**
* Returns the byte used to generate the character at a given
* position.
*/
@Override
public int getByteOffset(ByteBlock block, int pos) {
if (prefixEnabled) {
if (pos <= 3) {
return 0;
}
else if (pos < (2 + unitByteSize * 2)) {
return ((pos - 2) / 2);
}
return unitByteSize - 1;
}
if (pos < unitByteSize * 2) {
return (pos / 2);
}
return unitByteSize - 1;
return pos < symbolSize ? pos / 2 : groupSize - 1;
}
/**
* Given the byte offset into a unit, get the column position.
*/
@Override
public int getColumnPosition(ByteBlock block, int byteOffset) {
if (prefixEnabled) {
return byteOffset * 2 + 2;
}
return byteOffset * 2;
}
/**
* Gets the position where the cursor should be placed in
* order to do an 'overwrite' of data.
* @param position current position of the cursor within
* the data unit.
*/
public int getInsertionPosition(int pos) {
if (prefixEnabled) {
if (pos <= 2) {
return 2;
}
else if (pos < symbolSize) {
return pos;
}
else {
return symbolSize - 1;
}
}
if (pos <= 0) {
return 0;
}
else if (pos < symbolSize) {
return pos;
}
else {
return symbolSize - 1;
}
}
/**
* Gets the string representation at the given index in the block.
* @param block block to change
* @param index byte index into the block
* @throws ByteBlockAccessException if the block cannot be read
* @throws IndexOutOfBoundsException if index is not valid for the
* block
*/
@Override
public String getDataRepresentation(ByteBlock block, BigInteger index)
throws ByteBlockAccessException {
StringBuffer sb = new StringBuffer();
//System.out.println("representation: = " + block.getLocationRepresentation(index)+ ", index = " + index);
if (prefixEnabled) {
sb.append("0x");
byte[] bytes = new byte[groupSize];
int bytesRead = block.getBytes(bytes, index, groupSize);
if (bytesRead == 0) {
return fullSymbolErrorStr;
}
StringBuffer strBuff = new StringBuffer();
BigInteger byteIndex = index;
boolean qflag = false;
for (int idx = 0; idx < unitByteSize; idx++) {
try {
byte b = block.getByte(byteIndex);
strBuff.append(adjust(Integer.toHexString(b)));
byteIndex = byteIndex.add(BigInteger.ONE);
}
catch (ByteBlockAccessException bbae) {
if (idx == 0 || qflag) {
strBuff.append("??");
qflag = true;
}
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < bytesRead; i++) {
sb.append("%02x".formatted(Byte.toUnsignedInt(bytes[i])));
}
String str = strBuff.toString();
if (alphaCapsEnabled) {
str = str.toUpperCase();
}
sb.append(str);
return new String(sb);
return sb.toString();
}
/**
* Gets the string representation for numUnits at the given
* index in the block.
* @param block block to change
* @param index byte index into the block
* @param numUnits number of units to get
* @throws ByteBlockAccessException if the block cannot be read
* @throws IndexOutOfBoundsException if index is not valid for the
* block
*/
public String getDataRepresentation(ByteBlock block, BigInteger index, int numUnits)
throws ByteBlockAccessException {
int n;
StringBuffer sb = new StringBuffer();
System.out.println("representation: = " + block.getLocationRepresentation(index) +
", index = " + index);
for (n = 0; n < numUnits; n++, index = index.add(BigInteger.ONE)) {
String str = getDataRepresentation(block, index);
sb.append(str);
}
return new String(sb);
}
/**
* Returns true to allow values to be changed.
*/
@Override
public boolean isEditable() {
return true;
}
/**
* Get the number of characters separating units for display purposes.
*/
@Override
public int getUnitDelimiterSize() {
return 1;
}
/**
* Overwrite a value in a ByteBlock.
* @param block block to change
* @param index byte index into the block
* @param pos The position within the unit where c will be the
* new character.
* @param c new character to put at pos param
* @return true if the replacement is legal, false if the
* replacement value would not make sense for this format, e.g.
* attempt to put a 'z' in a hex unit.
* @throws ByteBlockAccessException if the block cannot be updated
* @throws IndexOutOfBoundsException if index is not valid for the
* block
*/
@Override
public boolean replaceValue(ByteBlock block, BigInteger index, int charPosition, char c)
throws ByteBlockAccessException {
@@ -268,8 +113,7 @@ public class HexFormatModel implements UniversalDataFormatModel {
if (GOOD_CHARS.indexOf(c) == -1) {
return false;
}
if ((prefixEnabled && (charPosition < 2 || charPosition >= symbolSize)) ||
(!prefixEnabled && (charPosition < 0 || charPosition >= symbolSize))) {
if (charPosition < 0 || charPosition >= symbolSize) {
return false;
}
@@ -294,45 +138,9 @@ public class HexFormatModel implements UniversalDataFormatModel {
return true;
}
/**
* @see ghidra.app.plugin.core.format.DataFormatModel#validateBytesPerLine(int)
*/
@Override
public boolean validateBytesPerLine(int bytesPerLine) {
return true;
}
/////////////////////////////////////////////////////////////////
// *** private methods ***
/////////////////////////////////////////////////////////////////
private String adjust(String value) {
StringBuffer sb = new StringBuffer();
int strLen = value.length();
if (strLen > 2) {
sb.append(value.substring(strLen - 2));
}
else {
int len = 2 - strLen;
for (int i = 0; i < len; i++) {
sb.append("0");
}
sb.append(value);
}
return (sb.toString());
}
/* (non-Javadoc)
* @see ghidra.app.plugin.format.DataFormatModel#getHelpLocation()
*/
@Override
public HelpLocation getHelpLocation() {
return new HelpLocation("ByteViewerPlugin", "Hex");
}
@Override
public void dispose() {
}
}

View File

@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -31,6 +31,6 @@ public class HexIntegerFormatModel extends HexValueFormatModel {
throws ByteBlockAccessException {
int i = block.getInt(index);
String str = Integer.toHexString(i);
return pad(str);
return DataFormatModel.pad(str, symbolSize);
}
}

View File

@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -31,6 +31,6 @@ public class HexLongFormatModel extends HexValueFormatModel {
throws ByteBlockAccessException {
long l = block.getLong(index);
String str = Long.toHexString(l);
return pad(str);
return DataFormatModel.pad(str, symbolSize);
}
}

View File

@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -17,6 +17,8 @@ package ghidra.app.plugin.core.format;
import java.math.BigInteger;
import ghidra.util.DataConverter;
/**
* Converts byte values to LongLong represented as an 16-byte/32-digit hex number.
*/
@@ -29,11 +31,13 @@ public class HexLongLongFormatModel extends HexValueFormatModel {
@Override
public String getDataRepresentation(ByteBlock block, BigInteger index)
throws ByteBlockAccessException {
long l0 = block.getLong(index);
String str0 = pad(Long.toHexString(l0));
long l1 = block.getLong(index.add(BigInteger.valueOf(8)));
String str1 = pad(Long.toHexString(l1));
String str = str1.substring(16) + str0.substring(16);
return pad(str);
byte[] bytes = new byte[nbytes];
if (block.getBytes(bytes, index, nbytes) != nbytes) {
return fullSymbolErrorStr;
}
DataConverter dc = DataConverter.getInstance(block.isBigEndian());
BigInteger val = dc.getBigInteger(bytes, nbytes, false);
return "%032x".formatted(val);
}
}

View File

@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -30,7 +30,7 @@ public class HexShortFormatModel extends HexValueFormatModel {
public String getDataRepresentation(ByteBlock block, BigInteger index)
throws ByteBlockAccessException {
short s = block.getShort(index);
String str = Integer.toHexString(s & 0xFFFF);
return pad(str);
String str = Integer.toHexString(Short.toUnsignedInt(s));
return DataFormatModel.pad(str, symbolSize);
}
}

View File

@@ -20,19 +20,21 @@ import java.math.BigInteger;
import ghidra.util.HelpLocation;
/**
* Converts byte values to value represented as a 2, 4, 8, or 16-byte hex number.
* Common base class for 2, 4, 8, or 16-byte hex number formats.
*/
public abstract class HexValueFormatModel implements UniversalDataFormatModel {
public abstract class HexValueFormatModel
implements UniversalDataFormatModel, MutableDataFormatModel {
protected String name;
private int symbolSize;
protected int nbytes;
protected final String name;
protected final int symbolSize;
protected final int nbytes;
protected final String fullSymbolErrorStr;
public HexValueFormatModel(String name, int nbytes) {
this.name = name;
this.nbytes = nbytes;
symbolSize = nbytes * 2;
this.symbolSize = nbytes * 2;
this.fullSymbolErrorStr = "??".repeat(nbytes);
}
@Override
@@ -76,11 +78,6 @@ public abstract class HexValueFormatModel implements UniversalDataFormatModel {
public abstract String getDataRepresentation(ByteBlock block, BigInteger index)
throws ByteBlockAccessException;
@Override
public boolean isEditable() {
return true;
}
@Override
public boolean replaceValue(ByteBlock block, BigInteger index, int charPosition, char c)
throws ByteBlockAccessException {
@@ -100,44 +97,11 @@ public abstract class HexValueFormatModel implements UniversalDataFormatModel {
return true;
}
@Override
public int getGroupSize() {
return 1;
}
/**
* Set the number of units in a group. This format does not
* support groups.
*/
@Override
public void setGroupSize(int groupSize) {
throw new UnsupportedOperationException("groups are not supported");
}
@Override
public int getUnitDelimiterSize() {
return 1;
}
@Override
public boolean validateBytesPerLine(int bytesPerLine) {
return bytesPerLine % nbytes == 0;
}
/**
* Returns value with leading zeros.
*/
protected String pad(String value) {
StringBuffer sb = new StringBuffer();
int len = symbolSize - value.length();
for (int i = 0; i < len; i++) {
sb.append("0");
}
sb.append(value);
return sb.toString();
}
/**
* adjust byte b to use either the upper 4 bits or
* the lower 4 bits of newb according to charPosition.
@@ -157,12 +121,6 @@ public abstract class HexValueFormatModel implements UniversalDataFormatModel {
@Override
public HelpLocation getHelpLocation() {
String anchor = name.replaceAll("\\s", "");
return new HelpLocation("ByteViewerPlugin", anchor);
return new HelpLocation("ByteViewerPlugin", name.replaceAll(" ", "")); // "Hex Long" -> "HexLong"
}
@Override
public void dispose() {
}
}

View File

@@ -15,10 +15,10 @@
*/
package ghidra.app.plugin.core.format;
import ghidra.util.HelpLocation;
import java.math.BigInteger;
import ghidra.util.HelpLocation;
/**
* Converts byte values to Integer representation in decimal format.
* This formatter does not allow editing.
@@ -28,8 +28,7 @@ public class IntegerFormatModel implements UniversalDataFormatModel {
private int symbolSize;
public IntegerFormatModel() {
symbolSize = 11; // 1 char for sign
this.symbolSize = 11; // 1 char for sign
}
/**
@@ -93,59 +92,8 @@ public class IntegerFormatModel implements UniversalDataFormatModel {
// determine what bytes to get
int i = block.getInt(index);
String str = Integer.toString(i);
return DataFormatModel.pad(Integer.toString(i), symbolSize, " ");
return pad(str);
}
/**
* Returns false to allow no values to be changed.
*/
@Override
public boolean isEditable() {
return false;
}
/**
* Overwrite a value in a ByteBlock.
* @param block block to change
* @param index byte index into the block
* @param pos The position within the unit where c will be the
* new character.
* @param c new character to put at pos param
* @return true if the replacement is legal, false if the
* replacement value would not make sense for this format, e.g.
* attempt to put a 'z' in a hex unit.
* @throws ByteBlockAccessException if the block cannot be updated
* @throws IndexOutOfBoundsException if index is not valid for the
* block
*/
@Override
public boolean replaceValue(ByteBlock block, BigInteger index, int pos, char c)
throws ByteBlockAccessException {
return false;
}
/**
* Get number of units in a group. A group may represent
* multiple units shown as one entity. This format does not
* support groups.
*/
@Override
public int getGroupSize() {
return 1;
}
/**
* Set the number of units in a group. This format does not
* support groups.
* @throws UnsupportedOperationException
*/
@Override
public void setGroupSize(int groupSize) {
throw new UnsupportedOperationException("groups are not supported");
}
/**
@@ -156,46 +104,8 @@ public class IntegerFormatModel implements UniversalDataFormatModel {
return 1;
}
/**
* @see ghidra.app.plugin.core.format.DataFormatModel#validateBytesPerLine(int)
*/
@Override
public boolean validateBytesPerLine(int bytesPerLine) {
return bytesPerLine % 4 == 0;
}
/////////////////////////////////////////////////////////////////
// *** private methods ***
/////////////////////////////////////////////////////////////////
/**
* Returns value with leading zeros if the value
* represents a positive number; returns value
* with leading blanks if the value represents a
* negative number.
*/
private String pad(String value) {
StringBuffer sb = new StringBuffer();
int len = symbolSize - value.length();
for (int i = 0; i < len; i++) {
sb.append(" ");
}
sb.append(value);
return (sb.toString());
}
/* (non-Javadoc)
* @see ghidra.app.plugin.format.DataFormatModel#getHelpLocation()
*/
@Override
public HelpLocation getHelpLocation() {
return new HelpLocation("ByteViewerPlugin", "Integer");
}
@Override
public void dispose() {
// nothing to do
}
}

View File

@@ -0,0 +1,42 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.format;
import java.math.BigInteger;
/**
* Optional interface for DataFormatModels that allows the user to modify byte values using
* the model's representation format.
*/
public interface MutableDataFormatModel extends DataFormatModel {
/**
* Overwrite a value in a ByteBlock.
* @param block block to change
* @param index byte index into the block
* @param pos The position within the unit where c will be the
* new character.
* @param c new character to put at pos param
* @return true if the replacement is legal, false if the
* replacement value would not make sense for this format, e.g.
* attempt to put a 'z' in a hex unit.
* @throws ByteBlockAccessException if the block cannot be updated
* @throws IndexOutOfBoundsException if index is not valid for the
* block
*/
boolean replaceValue(ByteBlock block, BigInteger index, int pos, char c)
throws ByteBlockAccessException;
}

View File

@@ -15,14 +15,14 @@
*/
package ghidra.app.plugin.core.format;
import ghidra.util.HelpLocation;
import java.math.BigInteger;
import ghidra.util.HelpLocation;
/**
* Converts byte values to Octal representation.
*/
public class OctalFormatModel implements UniversalDataFormatModel {
public class OctalFormatModel implements UniversalDataFormatModel, MutableDataFormatModel {
private int symbolSize;
private static final String GOOD_CHARS = "01234567";
@@ -90,32 +90,18 @@ public class OctalFormatModel implements UniversalDataFormatModel {
throws ByteBlockAccessException {
byte b = block.getByte(index);
int i = b;
i &= 0xff; // 0377
int i = Byte.toUnsignedInt(b);
String str = Integer.toOctalString(i);
String str = Integer.toOctalString(i); // "377" is max
if (str.length() > symbolSize) {
str = str.substring(str.length() - symbolSize);
}
return pad(str);
}
/**
* Returns true to allow values to be changed.
*/
@Override
public boolean isEditable() {
return (true);
return DataFormatModel.pad(str, symbolSize);
}
/**
* Overwrite a value in a ByteBlock.
* @param block block to change
* @param index byte index into the block
* @param pos The position within the unit where c will be the
* @param charPosition The position within the unit where c will be the
* new character.
* @param c new character to put at pos param
* @return true if the replacement is legal, false if the
@@ -164,26 +150,6 @@ public class OctalFormatModel implements UniversalDataFormatModel {
return true;
}
/**
* Get number of units in a group. A group may represent
* multiple units shown as one entity. This format does not
* support groups.
*/
@Override
public int getGroupSize() {
return 1;
}
/**
* Set the number of units in a group. This format does not
* support groups.
* @throws UnsupportedOperationException
*/
@Override
public void setGroupSize(int groupSize) {
throw new UnsupportedOperationException("groups are not supported");
}
/**
* Get the number of characters separating units.
*/
@@ -192,38 +158,8 @@ public class OctalFormatModel implements UniversalDataFormatModel {
return 1;
}
/**
* @see ghidra.app.plugin.core.format.DataFormatModel#validateBytesPerLine(int)
*/
@Override
public boolean validateBytesPerLine(int bytesPerLine) {
return true;
}
/////////////////////////////////////////////////////////////////
// *** private methods ***
/////////////////////////////////////////////////////////////////
private String pad(String value) {
StringBuffer sb = new StringBuffer();
int len = symbolSize - value.length();
for (int i = 0; i < len; i++) {
sb.append("0");
}
sb.append(value);
return (sb.toString());
}
/* (non-Javadoc)
* @see ghidra.app.plugin.format.DataFormatModel#getHelpLocation()
*/
@Override
public HelpLocation getHelpLocation() {
return new HelpLocation("ByteViewerPlugin", "Octal");
}
@Override
public void dispose() {
// nothing to do
}
}

View File

@@ -0,0 +1,27 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.format;
import java.math.BigInteger;
import ghidra.app.plugin.core.byteviewer.ByteViewerComponent;
/**
* Optional interface for {@link DataFormatModel}s that want to provide tooltip popups.
*/
public interface TooltipDataFormatModel extends DataFormatModel {
String getTooltip(ByteBlock block, BigInteger index, ByteViewerComponent comp);
}

View File

@@ -0,0 +1,516 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.byteviewer;
import static org.junit.Assert.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.util.*;
import java.util.List;
import javax.swing.JLabel;
import org.junit.After;
import org.junit.Before;
import docking.*;
import docking.action.*;
import docking.menu.ToolBarItemManager;
import docking.menu.ToolBarManager;
import docking.widgets.fieldpanel.FieldPanel;
import docking.widgets.fieldpanel.field.Field;
import docking.widgets.fieldpanel.field.SimpleTextField;
import docking.widgets.fieldpanel.support.FieldLocation;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
import ghidra.app.plugin.core.format.*;
import ghidra.app.services.GoToService;
import ghidra.app.services.ProgramManager;
import ghidra.app.util.viewer.field.OperandFieldFactory;
import ghidra.framework.plugintool.Plugin;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Listing;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.program.util.ProgramLocation;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
import ghidra.test.TestEnv;
public abstract class AbstractByteViewerPluginTest extends AbstractGhidraHeadedIntegrationTest {
protected TestEnv env;
protected PluginTool tool;
protected Program program;
protected ByteViewerPlugin plugin;
protected ByteViewerPanel panel;
protected CodeBrowserPlugin cbPlugin;
protected ProgramByteViewerComponentProvider provider;
protected Memory memory;
protected Listing listing;
@Before
public void setUp() throws Exception {
env = new TestEnv();
tool = env.showTool();
for (Class<? extends Plugin> pluginClass : getDefaultPlugins()) {
tool.addPlugin(pluginClass.getName());
}
tool.addPlugin(ByteViewerPlugin.class.getName());
plugin = env.getPlugin(ByteViewerPlugin.class);
provider = plugin.getProvider();
panel = provider.getByteViewerPanel();
tool.showComponentProvider(provider, true);
cbPlugin = env.getPlugin(CodeBrowserPlugin.class);
program = buildProgram();
memory = program.getMemory();
listing = program.getListing();
ProgramManager pm = tool.getService(ProgramManager.class);
pm.openProgram(program.getDomainFile());
}
@After
public void tearDown() throws Exception {
env.dispose();
}
abstract protected List<Class<? extends Plugin>> getDefaultPlugins();
abstract protected Program buildProgram() throws Exception;
protected void assertPosition(ByteViewerComponent view, String expectedFieldText,
int expectedColumn) {
String fieldText = runSwing(() -> {
Field field = view.getCurrentField();
String text = field.getText();
return text;
});
assertEquals(expectedFieldText, fieldText);
FieldLocation location = runSwing(() -> view.getCursorLocation());
assertEquals(expectedColumn, location.getCol());
}
protected void setEditMode(boolean b) {
ToggleDockingAction action = provider.getEditModeAction();
if (action.isSelected() != b) {
performAction(action);
assertTrue(action.isSelected() == b);
}
}
protected void gotoInPanel(Address addr) {
runSwing(() -> {
ProgramByteBlockSet blockset = (ProgramByteBlockSet) provider.getByteBlockSet();
ByteBlockInfo bbInfo = blockset.getByteBlockInfo(addr);
panel.setCursorLocation(bbInfo.getBlock(), bbInfo.getOffset(), bbInfo.getColumn());
});
}
protected void goTo(Address addr) {
GoToService goToService = tool.getService(GoToService.class);
goToService.goTo(new ProgramLocation(program, addr));
}
protected void goToByte(String addr) {
goToByte(addr(addr));
}
protected void goToByte(Address addr) {
ByteViewerComponent view = runSwing(() -> panel.getCurrentComponent());
goToByte(view, addr);
}
protected void goToByte(ByteViewerComponent view, Address addr) {
FieldLocation loc = getFieldLocation(addr);
runSwing(() -> {
view.setCursorPosition(loc.getIndex(), loc.getFieldNum(), 0, 0);
});
}
protected void setByteViewerLocation(Address address) throws Exception {
setByteViewerLocation(provider, address);
}
protected void setByteViewerLocation(ProgramByteViewerComponentProvider testProvider,
Address address) throws Exception {
ByteViewerPanel byteViewerPanel = testProvider.getByteViewerPanel();
ByteViewerComponent component = byteViewerPanel.getCurrentComponent();
FieldLocation byteViewerLocation = getFieldLocation(address);
runSwing(() -> {
component.setCursorPosition(byteViewerLocation.getIndex(),
byteViewerLocation.getFieldNum(), byteViewerLocation.getRow(),
byteViewerLocation.getCol());
component.scrollToCursor();
});
}
protected void setViewSelected(ByteViewerOptionsDialog dialog, String viewName,
boolean selected) {
runSwing(() -> dialog.setModelSelected(viewName, selected));
}
protected ByteViewerOptionsDialog launchByteViewerOptions() {
DockingAction action = provider.getOptionsAction();
assertTrue(action.isEnabled());
runSwing(() -> action.actionPerformed(new DefaultActionContext()), false);
waitForSwing();
ByteViewerOptionsDialog d = waitForDialogComponent(ByteViewerOptionsDialog.class);
return d;
}
protected ByteViewerComponent setView(String name) {
ByteViewerComponent view = getView(name);
setView(view);
return view;
}
protected void setView(ByteViewerComponent view) {
runSwing(() -> panel.setCurrentView(view));
}
protected ByteViewerComponent getView(String name) {
ByteViewerComponent c = runSwing(() -> panel.getComponentByName(name));
if (c == null) {
fail("Cannot find view '" + name + "'");
}
return c;
}
protected ByteViewerComponent getCurrentView() {
return panel.getCurrentComponent();
}
protected ByteField getField(String viewName, Address addr) {
return runSwing(() -> {
ByteViewerComponent c = panel.getComponentByName(viewName);
ProgramByteBlockSet blockset = (ProgramByteBlockSet) provider.getByteBlockSet();
ByteBlockInfo bbInfo = blockset.getByteBlockInfo(addr);
FieldLocation loc = c.getFieldLocation(bbInfo.getBlock(), bbInfo.getOffset());
return c.getField(loc.getIndex(), loc.getFieldNum());
});
}
protected ByteField getField(ByteViewerComponent c) {
return runSwing(() -> {
FieldLocation loc = c.getCursorLocation();
return c.getField(loc.getIndex(), loc.getFieldNum());
});
}
protected ByteField getField(ByteViewerComponent c, Address addr) {
return runSwing(() -> {
ProgramByteBlockSet blockset = (ProgramByteBlockSet) provider.getByteBlockSet();
ByteBlockInfo bbInfo = blockset.getByteBlockInfo(addr);
FieldLocation loc = c.getFieldLocation(bbInfo.getBlock(), bbInfo.getOffset());
return c.getField(loc.getIndex(), loc.getFieldNum());
});
}
protected FieldLocation getFieldLocation(Address addr) {
return runSwing(() -> {
ByteViewerComponent c = panel.getCurrentComponent();
ProgramByteBlockSet blockset = (ProgramByteBlockSet) provider.getByteBlockSet();
ByteBlockInfo bbInfo = blockset.getByteBlockInfo(addr);
return c.getFieldLocation(bbInfo.getBlock(), bbInfo.getOffset());
});
}
protected FieldLocation getFieldLocation(ByteViewerComponent c, Address addr) {
return runSwing(() -> {
ProgramByteBlockSet blockset = (ProgramByteBlockSet) provider.getByteBlockSet();
ByteBlockInfo bbInfo = blockset.getByteBlockInfo(addr);
return c.getFieldLocation(bbInfo.getBlock(), bbInfo.getOffset());
});
}
protected Address addr(long offset) {
return program.getImageBase().getNewAddress(offset);
}
protected Address addr(String offset) {
return program.getAddressFactory().getAddress(offset);
}
protected Address convertToAddr(ByteBlockInfo info) {
return ((ProgramByteBlockSet) provider.getByteBlockSet()).getAddress(info.getBlock(),
info.getOffset());
}
protected boolean byteBlockSelectionEquals(ByteBlockSelection b1, ByteBlockSelection b2) {
int nRanges = b1.getNumberOfRanges();
if (nRanges != b2.getNumberOfRanges()) {
return false;
}
for (int i = 0; i < nRanges; i++) {
ByteBlockRange range1 = b1.getRange(i);
ByteBlockRange range2 = b2.getRange(i);
if (!range1.equals(range2)) {
return false;
}
}
return true;
}
protected ByteViewerComponent findComponent(Container container, String name) {
Component[] c = container.getComponents();
for (Component element : c) {
if (element instanceof ByteViewerComponent) {
if (((ByteViewerComponent) element).getDataModel().getName().equals(name)) {
return (ByteViewerComponent) element;
}
}
else if (element instanceof Container) {
ByteViewerComponent bvc = findComponent((Container) element, name);
if (bvc != null) {
return bvc;
}
}
}
return null;
}
protected void loadViews(String... viewNames) {
enableViews(true, viewNames);
}
protected void enableViews(boolean b, String... viewNames) {
assertNotEquals(0, viewNames.length);
ByteViewerOptionsDialog dialog = launchByteViewerOptions();
for (String name : viewNames) {
setViewSelected(dialog, name, b);
}
pressButtonByText(dialog.getComponent(), "OK");
}
protected Container findContainer(Container parent, Class<?> theClass) {
Component[] c = parent.getComponents();
for (Component element : c) {
if (element.getClass() == theClass) {
return (Container) element;
}
if (element instanceof Container) {
Container container = findContainer((Container) element, theClass);
if (container != null) {
return container;
}
}
}
return null;
}
protected String findLabelStr(Container container, String name) {
Component[] c = container.getComponents();
for (Component element : c) {
if (element instanceof JLabel) {
if (name.equals(element.getName())) {
return ((JLabel) element).getText();
}
}
if (element instanceof Container) {
String str = findLabelStr((Container) element, name);
if (str != null) {
return str;
}
}
}
return null;
}
protected void setViewWidth(String name, int width) {
runSwing(() -> {
panel.setViewWidth(name, width);
});
}
protected int getViewWidth(String name) {
return runSwing(() -> {
return panel.getViewWidth(name);
});
}
protected void goTo(Address a, String fieldName) {
int row = 0;
int col = 0;
assertTrue(cbPlugin.goToField(a, fieldName, row, col));
}
protected void leftArrow() {
runSwing(() -> {
ByteViewerComponent view = panel.getCurrentComponent();
view.cursorLeft();
});
}
protected void rightArrow() {
runSwing(() -> {
ByteViewerComponent view = panel.getCurrentComponent();
view.cursorRight();
});
}
protected void pressKey(int modifiers, int keyCode, char keyChar) {
runSwing(() -> {
ByteViewerComponent c = panel.getCurrentComponent();
KeyEvent ev = new KeyEvent(c, 0, new Date().getTime(), modifiers, keyCode, keyChar);
FieldLocation loc = c.getCursorLocation();
c.keyPressed(ev, loc.getIndex(), loc.getFieldNum(), loc.getRow(), loc.getCol(),
c.getCurrentField());
});
}
protected void assertByteData(Address addr, int byteValue) throws MemoryAccessException {
byte b = memory.getByte(addr);
assertEquals(byteValue, Byte.toUnsignedInt(b));
}
protected void assertOffsetInfo(int offset, String addr) {
assertEquals(offset, runSwing(() -> provider.getConfigOptions().getOffset()).intValue());
assertEquals(Integer.toString(offset), findLabelStr(provider.getComponent(), "Offset"));
assertEquals(addr, findLabelStr(provider.getComponent(), "Insertion"));
}
protected void assertFieldLocationInfo(String addr, int index, int fieldNum) {
assertFieldLocationInfo(addr(addr), index, fieldNum);
}
protected void assertFieldLocationInfo(Address addr, int index, int fieldNum) {
FieldLocation floc = getFieldLocation(addr);
assertEquals(index, floc.getIndex().intValue());
assertEquals(fieldNum, floc.getFieldNum());
}
@SuppressWarnings("unchecked")
protected void assertOnlyOneProviderToolbarAction() {
DockingWindowManager dwm = tool.getWindowManager();
ActionToGuiMapper guiActions =
(ActionToGuiMapper) getInstanceField("actionToGuiMapper", dwm);
GlobalMenuAndToolBarManager menuManager =
(GlobalMenuAndToolBarManager) getInstanceField("menuAndToolBarManager", guiActions);
Map<WindowNode, WindowActionManager> windowToActionManagerMap =
(Map<WindowNode, WindowActionManager>) getInstanceField("windowToActionManagerMap",
menuManager);
DockingActionIf showAction =
(DockingActionIf) getInstanceField("showProviderAction", provider);
String actionName = showAction.getName();
List<DockingActionIf> matches = new ArrayList<>();
for (WindowActionManager actionManager : windowToActionManagerMap.values()) {
ToolBarManager toolbarManager =
(ToolBarManager) getInstanceField("toolBarMgr", actionManager);
Map<String, List<ToolBarItemManager>> groupToItems =
(Map<String, List<ToolBarItemManager>>) getInstanceField("groupToItemsMap",
toolbarManager);
Collection<List<ToolBarItemManager>> values = groupToItems.values();
for (List<ToolBarItemManager> list : values) {
for (ToolBarItemManager manager : list) {
DockingActionIf action = manager.getAction();
if (actionName.equals(action.getName())) {
matches.add(action);
}
}
}
}
assertEquals("Should only have 1 action on toolbar to show the provider", 1,
matches.size());
}
protected void goToOperand(String addr) {
goTo(addr(addr), OperandFieldFactory.FIELD_NAME);
}
protected void rightArrowListing() {
FieldPanel fp = cbPlugin.getFieldPanel();
runSwing(() -> fp.cursorRight());
}
protected void leftArrowListing() {
FieldPanel fp = cbPlugin.getFieldPanel();
runSwing(() -> fp.cursorLeft());
}
protected void assertListingPosition(String expectedFieldText, int expectedColumn) {
FieldPanel fp = cbPlugin.getFieldPanel();
String fieldText = runSwing(() -> {
Field field = fp.getCurrentField();
String text = field.getText();
return text;
});
assertEquals(expectedFieldText, fieldText);
FieldLocation location = runSwing(() -> fp.getCursorLocation());
assertEquals(expectedColumn, location.getCol());
}
protected void assertFieldColor(FieldLocation loc, Color expectedColor) {
assertFieldColor(loc, expectedColor, false);
}
protected void assertFieldColor(FieldLocation loc, Color expectedColor, boolean allowNull) {
ByteViewerComponent c = panel.getCurrentComponent();
ByteField field = c.getField(loc.getIndex(), loc.getFieldNum());
assertFieldColor(field, expectedColor, allowNull);
}
protected void assertFieldColor(SimpleTextField field, Color expectedColor) {
assertFieldColor(field, expectedColor, false);
}
protected void assertFieldColor(SimpleTextField field, Color expectedColor, boolean allowNull) {
Color c = field.getForeground();
if (c == null && expectedColor != null) {
if (allowNull) {
return;
}
fail("Field had no color: " + field);
}
assertEquals(expectedColor, c);
}
protected void assertColor(Color expectedColor, Color actualColor) {
assertColor(expectedColor, actualColor, false);
}
protected void assertColor(Color expectedColor, Color actualColor, boolean allowNull) {
if (actualColor == null && expectedColor != null) {
if (allowNull) {
return;
}
fail("Color was null, expected " + expectedColor);
}
assertEquals(expectedColor, actualColor);
}
protected void assertCursorColor(ByteViewerComponent c, Color expectedColor) {
assertColor(expectedColor, c.getFocusedCursorColor());
}
}

View File

@@ -17,26 +17,19 @@ package ghidra.app.plugin.core.byteviewer;
import static org.junit.Assert.*;
import java.awt.*;
import java.math.BigInteger;
import java.awt.Point;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.List;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableColumnModel;
import org.junit.*;
import org.junit.Test;
import docking.*;
import docking.DefaultActionContext;
import docking.action.DockingAction;
import docking.action.DockingActionIf;
import docking.menu.ToolBarItemManager;
import docking.menu.ToolBarManager;
import docking.widgets.EventTrigger;
import docking.widgets.fieldpanel.FieldPanel;
import docking.widgets.fieldpanel.field.Field;
import docking.widgets.fieldpanel.support.FieldLocation;
import docking.widgets.fieldpanel.support.FieldSelection;
import ghidra.app.events.ProgramSelectionPluginEvent;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
@@ -44,11 +37,7 @@ import ghidra.app.plugin.core.format.*;
import ghidra.app.plugin.core.navigation.*;
import ghidra.app.services.GoToService;
import ghidra.app.services.ProgramManager;
import ghidra.app.util.AddressInput;
import ghidra.app.util.bean.FixedBitSizeValueField;
import ghidra.app.util.viewer.field.OperandFieldFactory;
import ghidra.framework.plugintool.Plugin;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.database.ProgramBuilder;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSet;
@@ -56,41 +45,21 @@ import ghidra.program.model.data.StringDataType;
import ghidra.program.model.listing.CodeUnit;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramSelection;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
import ghidra.test.TestEnv;
/**
* Basic tests for the byte view plugin
*/
public class ByteViewerPlugin1Test extends AbstractGhidraHeadedIntegrationTest {
public class ByteViewerPlugin1Test extends AbstractByteViewerPluginTest {
private TestEnv env;
private PluginTool tool;
private Program program;
private ByteViewerPlugin plugin;
private ByteViewerPanel panel;
private CodeBrowserPlugin cbPlugin;
@Before
public void setUp() throws Exception {
env = new TestEnv();
tool = env.showTool();
tool.addPlugin(NavigationHistoryPlugin.class.getName());
tool.addPlugin(NextPrevAddressPlugin.class.getName());
tool.addPlugin(CodeBrowserPlugin.class.getName());
tool.addPlugin(GoToAddressLabelPlugin.class.getName());
tool.addPlugin(ByteViewerPlugin.class.getName());
plugin = env.getPlugin(ByteViewerPlugin.class);
tool.showComponentProvider(plugin.getProvider(), true);
cbPlugin = env.getPlugin(CodeBrowserPlugin.class);
program = buildNotepad();
ProgramManager pm = tool.getService(ProgramManager.class);
pm.openProgram(program.getDomainFile());
panel = plugin.getProvider().getByteViewerPanel();
@Override
protected List<Class<? extends Plugin>> getDefaultPlugins() {
return List.of(NavigationHistoryPlugin.class, NextPrevAddressPlugin.class,
CodeBrowserPlugin.class, GoToAddressLabelPlugin.class);
}
private Program buildNotepad() throws Exception {
@Override
protected Program buildProgram() throws Exception {
ProgramBuilder builder = new ProgramBuilder("notepad", ProgramBuilder._TOY);
builder.createMemory("test2", "0x1001000", 1000);
builder.createMemory("mem1", "0xf0001300", 1000);
@@ -99,29 +68,18 @@ public class ByteViewerPlugin1Test extends AbstractGhidraHeadedIntegrationTest {
builder.setBytes("0x1001000", "00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff");
// ascii - "message"
builder.setBytes("0x1001100", "6d 65 73 73 61 67 65 00");
builder.setBytes("0x1001100", "message\0".getBytes(StandardCharsets.US_ASCII));
builder.applyDataType("0x1001100", new StringDataType());
return builder.getProgram();
}
private Program build8051() throws Exception {
ProgramBuilder notepadBuilder = new ProgramBuilder("seg", ProgramBuilder._X86_16_REAL_MODE);
notepadBuilder.createMemory("mem1", "0000:0000", 0x8000);
return notepadBuilder.getProgram();
}
@After
public void tearDown() throws Exception {
env.dispose();
}
@Test
public void testOpenProgram() {
assertEquals("Hex", panel.getCurrentComponent().getDataModel().getName());
DockingActionIf action = getAction(plugin, "Byte Viewer Options");
DockingActionIf action = provider.getOptionsAction();
assertTrue(action.isEnabled());
assertEquals(1, panel.getNumberOfViews());
assertEquals(1, panel.getViewList().size());
}
@Test
@@ -149,7 +107,7 @@ public class ByteViewerPlugin1Test extends AbstractGhidraHeadedIntegrationTest {
});
waitForSwing();
program = buildNotepad();
program = buildProgram();
runSwing(() -> {
ProgramManager pm = tool.getService(ProgramManager.class);
pm.openProgram(program.getDomainFile());
@@ -161,19 +119,16 @@ public class ByteViewerPlugin1Test extends AbstractGhidraHeadedIntegrationTest {
@Test
public void testAddRemoveViews() throws Exception {
ByteViewerOptionsDialog dialog = launchByteViewerOptions();
setViewSelected(dialog, "Ascii", true);
setViewSelected(dialog, "Octal", true);
pressButtonByText(dialog.getComponent(), "OK");
loadViews("Chars", "Octal");
assertEquals(3, panel.getNumberOfViews());
assertEquals(3, panel.getViewList().size());
List<String> names = panel.getViewNamesInDisplayOrder();
assertEquals(3, names.size());
Set<String> viewNames = new HashSet<>(names);
assertTrue(viewNames.contains("Hex"));
assertTrue(viewNames.contains("Octal"));
assertTrue(viewNames.contains("Ascii"));
assertTrue(viewNames.contains("Chars"));
// verify cursor position is the same across all views
// verify the view is visible
@@ -181,7 +136,7 @@ public class ByteViewerPlugin1Test extends AbstractGhidraHeadedIntegrationTest {
assertTrue(bc.isVisible());
ByteBlockInfo bbInfo = bc.getViewerCursorLocation();
bc = findComponent(panel, "Ascii");
bc = findComponent(panel, "Chars");
assertTrue(bc.isVisible());
assertEquals(bbInfo, bc.getViewerCursorLocation());
@@ -189,27 +144,23 @@ public class ByteViewerPlugin1Test extends AbstractGhidraHeadedIntegrationTest {
assertTrue(bc.isVisible());
assertEquals(bbInfo, bc.getViewerCursorLocation());
dialog = launchByteViewerOptions();
setViewSelected(dialog, "Ascii", false);
setViewSelected(dialog, "Hex", false);
pressButtonByText(dialog.getComponent(), "OK");
enableViews(false, "Chars", "Hex");
assertEquals(1, panel.getNumberOfViews());
assertEquals(1, panel.getViewList().size());
names = panel.getViewNamesInDisplayOrder();
assertEquals(1, names.size());
assertEquals("Octal", names.get(0));
}
@Test
public void testSetSelection() throws Exception {
loadViews("Ascii", "Octal");
loadViews("Chars", "Octal");
final FieldSelection fsel = new FieldSelection();
fsel.addRange(0, 0, 8, 7);
ByteViewerComponent ascii = getView("Ascii");
ByteViewerComponent ascii = getView("Chars");
setView(ascii);
runSwing(() -> {
ascii.selectionChanged(fsel, EventTrigger.GUI_ACTION);
@@ -225,7 +176,7 @@ public class ByteViewerPlugin1Test extends AbstractGhidraHeadedIntegrationTest {
// convert bsel to an address set
AddressSet set =
((ProgramByteBlockSet) plugin.getProvider().getByteBlockSet()).getAddressSet(bsel);
((ProgramByteBlockSet) provider.getByteBlockSet()).getAddressSet(bsel);
// subtract one address from code browser selection since it is on
// a code unit boundary
@@ -236,7 +187,7 @@ public class ByteViewerPlugin1Test extends AbstractGhidraHeadedIntegrationTest {
@Test
public void testSetSelectionWithMouse() throws Exception {
loadViews("Ascii", "Octal");
loadViews("Chars", "Octal");
ByteViewerComponent c = panel.getCurrentComponent();
GoToService goToService = tool.getService(GoToService.class);
@@ -244,7 +195,7 @@ public class ByteViewerPlugin1Test extends AbstractGhidraHeadedIntegrationTest {
Point startPoint = c.getCursorPoint();
goToByte("0x010010bc");
goTo(addr(0x010010bc));
Point endPoint = c.getCursorPoint();
dragMouse(c, 1, startPoint.x, startPoint.y, endPoint.x, endPoint.y, 0);
@@ -258,14 +209,13 @@ public class ByteViewerPlugin1Test extends AbstractGhidraHeadedIntegrationTest {
ByteBlockSelection bsel = panel.getViewerSelection();
// convert bsel to an address set
AddressSet set =
((ProgramByteBlockSet) plugin.getProvider().getByteBlockSet()).getAddressSet(bsel);
AddressSet set = ((ProgramByteBlockSet) provider.getByteBlockSet()).getAddressSet(bsel);
assertTrue(psel.hasSameAddresses(set));
}
@Test
public void testProcessSelectionEvent() throws Exception {
loadViews("Ascii", "Octal");
loadViews("Chars", "Octal");
ByteViewerComponent c = panel.getCurrentComponent();
@@ -281,7 +231,7 @@ public class ByteViewerPlugin1Test extends AbstractGhidraHeadedIntegrationTest {
c = findComponent(panel, "Octal");
assertTrue(byteBlockSelectionEquals(bsel, c.getViewerSelection()));
c = findComponent(panel, "Ascii");
c = findComponent(panel, "Chars");
assertTrue(byteBlockSelectionEquals(bsel, c.getViewerSelection()));
ProgramSelection psel = cbPlugin.getCurrentSelection();
@@ -289,7 +239,7 @@ public class ByteViewerPlugin1Test extends AbstractGhidraHeadedIntegrationTest {
bsel = panel.getViewerSelection();
// convert bsel to an address set
set = ((ProgramByteBlockSet) plugin.getProvider().getByteBlockSet()).getAddressSet(bsel);
set = ((ProgramByteBlockSet) provider.getByteBlockSet()).getAddressSet(bsel);
assertTrue(psel.hasSameAddresses(set));
}
@@ -304,19 +254,19 @@ public class ByteViewerPlugin1Test extends AbstractGhidraHeadedIntegrationTest {
});
ByteViewerComponent c = panel.getCurrentComponent();
ByteBlockSelection sel = c.getViewerSelection();
loadViews("Ascii", "Octal");
loadViews("Chars", "Octal");
// verify that the selections are the same after adding views
ByteViewerComponent octalC = findComponent(panel, "Octal");
assertTrue(byteBlockSelectionEquals(sel, octalC.getViewerSelection()));
ByteViewerComponent asciiC = findComponent(panel, "Ascii");
ByteViewerComponent asciiC = findComponent(panel, "Chars");
assertTrue(byteBlockSelectionEquals(sel, asciiC.getViewerSelection()));
}
@Test
public void testMultiRangeSelection() throws Exception {
loadViews("Ascii", "Octal");
loadViews("Chars", "Octal");
final FieldSelection fsel = new FieldSelection();
fsel.addRange(0, 0, 4, 7);
@@ -332,21 +282,21 @@ public class ByteViewerPlugin1Test extends AbstractGhidraHeadedIntegrationTest {
ByteViewerComponent octalC = findComponent(panel, "Octal");
assertTrue(byteBlockSelectionEquals(sel, octalC.getViewerSelection()));
ByteViewerComponent asciiC = findComponent(panel, "Ascii");
ByteViewerComponent asciiC = findComponent(panel, "Chars");
assertTrue(byteBlockSelectionEquals(sel, asciiC.getViewerSelection()));
}
@Test
public void testLocationChanges() throws Exception {
loadViews("Ascii", "Octal");
loadViews("Chars", "Octal");
ByteViewerComponent c = panel.getCurrentComponent();
goToByte("0x01001004");
goTo(addr(0x01001004));
ByteBlockInfo info = c.getViewerCursorLocation();
ByteViewerComponent ascii = getView("Ascii");
ByteViewerComponent ascii = getView("Chars");
assertEquals(info, ascii.getViewerCursorLocation());
ByteViewerComponent octal = getView("Octal");
assertEquals(info, octal.getViewerCursorLocation());
@@ -357,7 +307,7 @@ public class ByteViewerPlugin1Test extends AbstractGhidraHeadedIntegrationTest {
assertEquals(addr, cbPlugin.getCurrentAddress());
goToByte("0x01001081");
goTo(addr(0x01001081));
info = c.getViewerCursorLocation();
assertEquals(info, ascii.getViewerCursorLocation());
@@ -370,7 +320,7 @@ public class ByteViewerPlugin1Test extends AbstractGhidraHeadedIntegrationTest {
CodeUnit cu = program.getListing().getCodeUnitContaining(addr);
assertEquals(cu.getMinAddress(), cbPlugin.getCurrentAddress());
goToByte("0xf0001300");
goTo(addr(0xf0001300));
info = c.getViewerCursorLocation();
assertEquals(info, ascii.getViewerCursorLocation());
@@ -428,7 +378,7 @@ public class ByteViewerPlugin1Test extends AbstractGhidraHeadedIntegrationTest {
ByteViewerComponent octal = getView("Octal");
setView(hex);
goToByte("0x01001004");
goTo(addr(0x01001004));
assertPosition(hex, "44", 0);
assertPosition(octal, "104", 0);
@@ -451,7 +401,7 @@ public class ByteViewerPlugin1Test extends AbstractGhidraHeadedIntegrationTest {
ByteViewerComponent octal = getView("Octal");
setView(octal);
goToByte("0x01001004");
goTo(addr(0x01001004));
assertPosition(hex, "44", 0);
assertPosition(octal, "104", 0);
@@ -474,7 +424,7 @@ public class ByteViewerPlugin1Test extends AbstractGhidraHeadedIntegrationTest {
ByteViewerComponent octal = getView("Octal");
setView(octal);
goToByte("0x01001005");
goTo(addr(0x01001005));
assertPosition(hex, "55", 0);
assertPosition(octal, "125", 0);
@@ -527,70 +477,54 @@ public class ByteViewerPlugin1Test extends AbstractGhidraHeadedIntegrationTest {
public void testBytesPerLinesChange() throws Exception {
ByteViewerOptionsDialog d = launchByteViewerOptions();
FixedBitSizeValueField field =
(FixedBitSizeValueField) getInstanceField("bytesPerLineField", d);
runSwing(() -> field.setValue(BigInteger.valueOf(10)));
runSwing(() -> d.setBytesPerLine(10));
pressButtonByText(d.getComponent(), "OK");
waitForSwing();
// verify that the bytes per line is 10
assertEquals(10, plugin.getProvider().getBytesPerLine());
ByteViewerConfigOptions configOptions = provider.getConfigOptions();
assertEquals(10, configOptions.getBytesPerLine());
ByteViewerComponent c = panel.getCurrentComponent();
assertEquals(10, c.getNumberOfFields());
assertEquals(8, plugin.getProvider().getOffset());
assertEquals(10, runSwing(() -> c.getNumberOfFields()).intValue());
}
@Test
public void testSetAlignedAddress() throws Exception {
public void testOffsetChange() {
goTo(addr(0x0100100b));
goToByte("0x0100100b");
int offsetOfAddr = 16 /* bytes_per_line*/ - (0x0100100b - 0x1001000); // == 5
ByteViewerOptionsDialog d = launchByteViewerOptions();
AddressInput ai = (AddressInput) getInstanceField("addressInputField", d);
// verify that the text field has the min address since the
// current offset is 0
assertEquals(program.getMinAddress(), ai.getAddress());
runSwing(() -> ai.setText("0100100b"));
runSwing(() -> d.setOffset(offsetOfAddr));
pressButtonByText(d.getComponent(), "OK");
// verify that offset label on the plugin shows '5'
assertEquals(5, plugin.getProvider().getOffset());
assertEquals("5", findLabelStr(plugin.getProvider().getComponent(), "Offset"));
assertEquals("0100100b", findLabelStr(plugin.getProvider().getComponent(), "Insertion"));
FieldLocation floc = getFieldLocation(addr(0x100101b));
assertEquals(2, floc.getIndex().intValue());
assertEquals(0, floc.getFieldNum());
// verify that offset label on the plugin shows the new offset
assertOffsetInfo(5, "0100100b");
floc = getFieldLocation(addr(0x100102b));
assertEquals(3, floc.getIndex().intValue());
assertEquals(0, floc.getFieldNum());
assertFieldLocationInfo(addr(0x0100100b), 1, 0 /* first field of the row */);
assertFieldLocationInfo(addr(0x0100102b), 3, 0 /* first field of the row */);
}
@Test
public void testSetBaseAddressSegmented() throws Exception {
public void testOffsetShiftViaToolbarButtonActions() {
DockingAction shiftLeftAction = provider.getShiftLeftAction();
DockingAction shiftRightAction = provider.getShiftRightAction();
ProgramManager pm = tool.getService(ProgramManager.class);
pm.closeProgram();
goTo(addr(0x0100100b));
assertFieldLocationInfo(addr(0x0100100b), 0, 0xb);
waitForSwing();
program = build8051();
pm.openProgram(program.getDomainFile());
ByteViewerOptionsDialog d = launchByteViewerOptions();
AddressInput ai = (AddressInput) getInstanceField("addressInputField", d);
// verify that the text field has the min address since the
// current offset is 0
assertEquals(program.getMinAddress(), ai.getAddress());
runSwing(() -> ai.setText("0000:0c06"));
pressButtonByText(d.getComponent(), "OK");
assertEquals(10, plugin.getProvider().getOffset());
for (int i = 0; i < 0xb; i++) {
runSwing(() -> shiftLeftAction.actionPerformed(new DefaultActionContext()));
assertOffsetInfo(16 - i - 1, "0100100b");
assertFieldLocationInfo(addr(0x0100100b), 1, 0xb - i - 1);
}
for (int i = 0xb - 1; i > 0; i--) {
runSwing(() -> shiftRightAction.actionPerformed(new DefaultActionContext()));
assertOffsetInfo(16 - i, "0100100b");
assertFieldLocationInfo(addr(0x0100100b), 1, 0xb - i);
}
}
@Test
@@ -598,30 +532,32 @@ public class ByteViewerPlugin1Test extends AbstractGhidraHeadedIntegrationTest {
ByteViewerComponent c = panel.getCurrentComponent();
panel.setCurrentView(c); // force component to have focus
assertEquals(16, runSwing(() -> c.getNumberOfFields()).intValue());
goToByte("0x0100100b");
goTo(addr(0x0100100b));
ByteViewerOptionsDialog dialog = launchByteViewerOptions();
FixedBitSizeValueField field =
(FixedBitSizeValueField) getInstanceField("groupSizeField", dialog);
// verify that the text field has the min address since the
// current offset is 0
runSwing(() -> field.setValue(BigInteger.valueOf(2)));
runSwing(() -> dialog.setHexGroupSize(2));
pressButtonByText(dialog.getComponent(), "OK");
assertEquals(2, c.getDataModel().getGroupSize());
DataFormatModel dataModel = c.getDataModel();
if (!(dataModel instanceof HexFormatModel hexModel)) {
fail();
return;
}
assertEquals(2, runSwing(() -> hexModel.getHexGroupSize()).intValue());
assertEquals(8, runSwing(() -> c.getNumberOfFields()).intValue());
}
@Test
public void testReorderViews() throws Exception {
loadViews("Ascii", "Octal");
loadViews("Chars", "Octal");
List<String> names = panel.getViewNamesInDisplayOrder();
assertEquals("Hex", names.get(0));
assertEquals("Octal", names.get(1));
assertEquals("Ascii", names.get(2));
assertEquals("Chars", names.get(2));
final JTableHeader columnHeader = (JTableHeader) findContainer(panel, JTableHeader.class);
// move column 3 to 2
@@ -632,7 +568,7 @@ public class ByteViewerPlugin1Test extends AbstractGhidraHeadedIntegrationTest {
names = panel.getViewNamesInDisplayOrder();
assertEquals("Hex", names.get(0));
assertEquals("Ascii", names.get(1));
assertEquals("Chars", names.get(1));
assertEquals("Octal", names.get(2));
// move column 2 to 1
@@ -641,7 +577,7 @@ public class ByteViewerPlugin1Test extends AbstractGhidraHeadedIntegrationTest {
columnModel.moveColumn(2, 1);
});
names = panel.getViewNamesInDisplayOrder();
assertEquals("Ascii", names.get(0));
assertEquals("Chars", names.get(0));
assertEquals("Hex", names.get(1));
assertEquals("Octal", names.get(2));
@@ -649,29 +585,29 @@ public class ByteViewerPlugin1Test extends AbstractGhidraHeadedIntegrationTest {
@Test
public void testResizeViews() {
loadViews("Ascii", "Octal");
assertNotEquals(200, getViewWidth("Ascii"));
loadViews("Chars", "Octal");
assertNotEquals(200, getViewWidth("Chars"));
setViewWidth("Ascii", 200);
setViewWidth("Chars", 200);
assertEquals(200, panel.getViewWidth("Ascii"));
assertEquals(200, panel.getViewWidth("Chars"));
}
@Test
public void testResizeViewsSaveState() {
loadViews("Ascii", "Octal");
assertNotEquals(200, getViewWidth("Ascii"));
loadViews("Chars", "Octal");
assertNotEquals(200, getViewWidth("Chars"));
setViewWidth("Ascii", 200);
setViewWidth("Chars", 200);
env.saveRestoreToolState();
assertEquals(200, panel.getViewWidth("Ascii"));
assertEquals(200, panel.getViewWidth("Chars"));
}
@Test
public void testReorderViewsSaveState() throws Exception {
loadViews("Ascii", "Octal");
loadViews("Chars", "Octal");
final JTableHeader columnHeader = (JTableHeader) findContainer(panel, JTableHeader.class);
// move column 1 to 0
@@ -699,274 +635,4 @@ public class ByteViewerPlugin1Test extends AbstractGhidraHeadedIntegrationTest {
assertOnlyOneProviderToolbarAction();
}
//==================================================================================================
// Private Methods
//==================================================================================================
@SuppressWarnings("unchecked")
private void assertOnlyOneProviderToolbarAction() {
DockingWindowManager dwm = tool.getWindowManager();
ActionToGuiMapper guiActions =
(ActionToGuiMapper) getInstanceField("actionToGuiMapper", dwm);
GlobalMenuAndToolBarManager menuManager =
(GlobalMenuAndToolBarManager) getInstanceField("menuAndToolBarManager", guiActions);
Map<WindowNode, WindowActionManager> windowToActionManagerMap =
(Map<WindowNode, WindowActionManager>) getInstanceField("windowToActionManagerMap",
menuManager);
ProgramByteViewerComponentProvider provider = plugin.getProvider();
DockingActionIf showAction =
(DockingActionIf) getInstanceField("showProviderAction", provider);
String actionName = showAction.getName();
List<DockingActionIf> matches = new ArrayList<>();
for (WindowActionManager actionManager : windowToActionManagerMap.values()) {
ToolBarManager toolbarManager =
(ToolBarManager) getInstanceField("toolBarMgr", actionManager);
Map<String, List<ToolBarItemManager>> groupToItems =
(Map<String, List<ToolBarItemManager>>) getInstanceField("groupToItemsMap",
toolbarManager);
Collection<List<ToolBarItemManager>> values = groupToItems.values();
for (List<ToolBarItemManager> list : values) {
for (ToolBarItemManager manager : list) {
DockingActionIf action = manager.getAction();
if (actionName.equals(action.getName())) {
matches.add(action);
}
}
}
}
assertEquals("Should only have 1 action on toolbar to show the provider", 1,
matches.size());
}
private void goToOperand(String addr) {
goTo(addr(addr), OperandFieldFactory.FIELD_NAME);
}
private void goTo(Address a, String fieldName) {
int row = 0;
int col = 0;
assertTrue(cbPlugin.goToField(a, fieldName, row, col));
}
private void leftArrow() {
runSwing(() -> {
ByteViewerComponent view = panel.getCurrentComponent();
view.cursorLeft();
});
}
private void rightArrow() {
runSwing(() -> {
ByteViewerComponent view = panel.getCurrentComponent();
view.cursorRight();
});
}
private void rightArrowListing() {
FieldPanel fp = cbPlugin.getFieldPanel();
runSwing(() -> fp.cursorRight());
}
private void leftArrowListing() {
FieldPanel fp = cbPlugin.getFieldPanel();
runSwing(() -> fp.cursorLeft());
}
private void assertListingPosition(String expectedFieldText, int expectedColumn) {
FieldPanel fp = cbPlugin.getFieldPanel();
String fieldText = runSwing(() -> {
Field field = fp.getCurrentField();
String text = field.getText();
return text;
});
assertEquals(expectedFieldText, fieldText);
FieldLocation location = runSwing(() -> fp.getCursorLocation());
assertEquals(expectedColumn, location.getCol());
}
private void assertPosition(ByteViewerComponent view, String expectedFieldText,
int expectedColumn) {
String fieldText = runSwing(() -> {
Field field = view.getCurrentField();
String text = field.getText();
return text;
});
assertEquals(expectedFieldText, fieldText);
FieldLocation location = runSwing(() -> view.getCursorLocation());
assertEquals(expectedColumn, location.getCol());
}
private void goToByte(String addr) {
goToByte(addr(addr));
}
private void goToByte(Address addr) {
ByteViewerComponent view = runSwing(() -> panel.getCurrentComponent());
goToByte(view, addr);
}
private void goToByte(ByteViewerComponent view, Address addr) {
FieldLocation loc = getFieldLocation(addr);
runSwing(() -> {
view.setCursorPosition(loc.getIndex(), loc.getFieldNum(), 0, 0);
});
}
private void setViewSelected(ByteViewerOptionsDialog dialog, String viewName,
boolean selected) {
Map<?, ?> checkboxMap = (Map<?, ?>) getInstanceField("checkboxMap", dialog);
JCheckBox checkbox = (JCheckBox) checkboxMap.get(viewName);
checkbox.setSelected(selected);
}
private ByteViewerOptionsDialog launchByteViewerOptions() {
final DockingActionIf action = getAction(plugin, "Byte Viewer Options");
assertTrue(action.isEnabled());
runSwing(() -> action.actionPerformed(new DefaultActionContext()), false);
waitForSwing();
ByteViewerOptionsDialog d = waitForDialogComponent(ByteViewerOptionsDialog.class);
return d;
}
private void setView(ByteViewerComponent view) {
runSwing(() -> panel.setCurrentView(view));
}
private ByteViewerComponent getView(String name) {
List<ByteViewerComponent> views = runSwing(() -> panel.getViewList());
for (ByteViewerComponent viewer : views) {
if (viewer.getName().equals(name)) {
return viewer;
}
}
fail("Cannot find view '" + name + "'");
return null;
}
private FieldLocation getFieldLocation(Address addr) {
return runSwing(() -> {
ByteViewerComponent c = panel.getCurrentComponent();
ProgramByteBlockSet blockset =
(ProgramByteBlockSet) plugin.getProvider().getByteBlockSet();
ByteBlockInfo bbInfo = blockset.getByteBlockInfo(addr);
return c.getFieldLocation(bbInfo.getBlock(), bbInfo.getOffset());
});
}
private Address addr(long offset) {
return addr(Long.toHexString(offset));
}
private Address addr(String offset) {
return program.getAddressFactory().getAddress(offset);
}
private Address convertToAddr(ByteBlockInfo info) {
return ((ProgramByteBlockSet) plugin.getProvider().getByteBlockSet())
.getAddress(info.getBlock(), info.getOffset());
}
private boolean byteBlockSelectionEquals(ByteBlockSelection b1, ByteBlockSelection b2) {
int nRanges = b1.getNumberOfRanges();
if (nRanges != b2.getNumberOfRanges()) {
return false;
}
for (int i = 0; i < nRanges; i++) {
ByteBlockRange range1 = b1.getRange(i);
ByteBlockRange range2 = b2.getRange(i);
if (!range1.equals(range2)) {
return false;
}
}
return true;
}
private ByteViewerComponent findComponent(Container container, String name) {
Component[] c = container.getComponents();
for (Component element : c) {
if (element instanceof ByteViewerComponent) {
if (((ByteViewerComponent) element).getDataModel().getName().equals(name)) {
return (ByteViewerComponent) element;
}
}
else if (element instanceof Container) {
ByteViewerComponent bvc = findComponent((Container) element, name);
if (bvc != null) {
return bvc;
}
}
}
return null;
}
private void loadViews(String... viewNames) {
assertNotEquals(0, viewNames.length);
ByteViewerOptionsDialog dialog = launchByteViewerOptions();
for (String name : viewNames) {
setViewSelected(dialog, name, true);
}
pressButtonByText(dialog.getComponent(), "OK");
}
private Container findContainer(Container parent, Class<?> theClass) {
Component[] c = parent.getComponents();
for (Component element : c) {
if (element.getClass() == theClass) {
return (Container) element;
}
if (element instanceof Container) {
Container container = findContainer((Container) element, theClass);
if (container != null) {
return container;
}
}
}
return null;
}
private String findLabelStr(Container container, String name) {
Component[] c = container.getComponents();
for (Component element : c) {
if (element instanceof JLabel) {
if (name.equals(((JLabel) element).getName())) {
return ((JLabel) element).getText();
}
}
if (element instanceof Container) {
String str = findLabelStr((Container) element, name);
if (str != null) {
return str;
}
}
}
return null;
}
private void setViewWidth(String name, int width) {
runSwing(() -> {
panel.setViewWidth(name, width);
});
}
private int getViewWidth(String name) {
return runSwing(() -> {
return panel.getViewWidth("Ascii");
});
}
}

View File

@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -15,86 +15,42 @@
*/
package ghidra.app.plugin.core.byteviewer;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.*;
import java.awt.Point;
import java.util.List;
import javax.swing.SwingUtilities;
import org.junit.*;
import org.junit.Test;
import docking.widgets.fieldpanel.support.FieldLocation;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
import ghidra.app.plugin.core.format.ByteBlockInfo;
import ghidra.app.plugin.core.format.ByteBlockSelection;
import ghidra.app.plugin.core.navigation.*;
import ghidra.app.services.GoToService;
import ghidra.app.services.ProgramManager;
import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.plugintool.Plugin;
import ghidra.program.database.ProgramBuilder;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSet;
import ghidra.program.model.listing.CodeUnit;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramSelection;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
import ghidra.test.TestEnv;
/**
* Tests for what the byte viewer should do and not when it is not visible.
*
*/
public class ByteViewerPlugin3Test extends AbstractGhidraHeadedIntegrationTest {
private TestEnv env;
private PluginTool tool;
private Program program;
private ByteViewerPlugin plugin;
private ByteViewerPanel panel;
private CodeBrowserPlugin cbPlugin;
public class ByteViewerPlugin3Test extends AbstractByteViewerPluginTest {
/**
* Constructor for ByteViewerPlugin4Test.
* @param arg0
*/
public ByteViewerPlugin3Test() {
super();
@Override
protected List<Class<? extends Plugin>> getDefaultPlugins() {
return List.of(GoToAddressLabelPlugin.class, NavigationHistoryPlugin.class,
NextPrevAddressPlugin.class, CodeBrowserPlugin.class);
}
/*
* @see TestCase#setUp()
*/
@Before
public void setUp() throws Exception {
env = new TestEnv();
try {
tool = env.getTool();
tool.addPlugin(GoToAddressLabelPlugin.class.getName());
tool.addPlugin(NavigationHistoryPlugin.class.getName());
tool.addPlugin(NextPrevAddressPlugin.class.getName());
tool.addPlugin(CodeBrowserPlugin.class.getName());
tool.addPlugin(ByteViewerPlugin.class.getName());
plugin = env.getPlugin(ByteViewerPlugin.class);
tool.showComponentProvider(plugin.getProvider(), true);
cbPlugin = env.getPlugin(CodeBrowserPlugin.class);
program = buildNotepad();
ProgramManager pm = tool.getService(ProgramManager.class);
pm.openProgram(program.getDomainFile());
panel = plugin.getProvider().getByteViewerPanel();
waitForSwing();
env.showTool();
}
catch (Exception e) {
env.dispose();
throw e;
}
}
private Program buildNotepad() throws Exception {
@Override
protected Program buildProgram() throws Exception {
ProgramBuilder builder = new ProgramBuilder("notepad", ProgramBuilder._TOY);
builder.createMemory("test2", "0x1001000", 0x2000);
Program p = builder.getProgram();
@@ -102,32 +58,20 @@ public class ByteViewerPlugin3Test extends AbstractGhidraHeadedIntegrationTest {
return p;
}
/*
* @see TestCase#tearDown()
*/
@After
public void tearDown() throws Exception {
env.release(program);
env.dispose();
}
@Test
@Test
public void testSetVisible() throws Exception {
GoToService goToService = tool.getService(GoToService.class);
goToService.goTo(getAddr(0x01001004));
final ByteViewerComponent c = panel.getCurrentComponent();
goTo(addr(0x01001004));
ByteViewerComponent c = panel.getCurrentComponent();
ByteBlockInfo info = c.getViewerCursorLocation();
assertEquals(cbPlugin.getCurrentAddress(), convertToAddr(info));
// make a selection in the Code Browser
Point startPoint = c.getCursorPoint();
SwingUtilities.invokeAndWait(new Runnable() {
@Override
public void run() {
FieldLocation loc = getFieldLocation(getAddr(0x010010bc));
c.setCursorPosition(loc.getIndex(), loc.getFieldNum(), loc.getRow(), loc.getCol());
}
runSwing(() -> {
FieldLocation loc = getFieldLocation(addr(0x010010bc));
c.setCursorPosition(loc.getIndex(), loc.getFieldNum(), loc.getRow(), loc.getCol());
});
Point endPoint = c.getCursorPoint();
@@ -140,7 +84,7 @@ public class ByteViewerPlugin3Test extends AbstractGhidraHeadedIntegrationTest {
// convert bsel to an address set
AddressSet set =
((ProgramByteBlockSet) plugin.getProvider().getByteBlockSet()).getAddressSet(bsel);
((ProgramByteBlockSet) provider.getByteBlockSet()).getAddressSet(bsel);
assertTrue(psel.hasSameAddresses(set));
info = c.getViewerCursorLocation();
@@ -160,50 +104,28 @@ public class ByteViewerPlugin3Test extends AbstractGhidraHeadedIntegrationTest {
bsel = panel.getViewerSelection();
// convert bsel to an address set
set = ((ProgramByteBlockSet) plugin.getProvider().getByteBlockSet()).getAddressSet(bsel);
set = ((ProgramByteBlockSet) provider.getByteBlockSet()).getAddressSet(bsel);
assertTrue(psel.hasSameAddresses(set));
}
@Test
@Test
public void testNotVisible() throws Exception {
GoToService goToService = tool.getService(GoToService.class);
Address addr = getAddr(0x01002000);
goToService.goTo(addr);
final ByteViewerComponent c = panel.getCurrentComponent();
Address addr = addr(0x01002000);
goTo(addr);
ByteViewerComponent c = panel.getCurrentComponent();
ByteBlockInfo info = c.getViewerCursorLocation();
assertEquals(cbPlugin.getCurrentAddress(), convertToAddr(info));
showComponent(false);
addr = getAddr(0x01002500);
goToService.goTo(addr);
addr = addr(0x01002500);
goTo(addr);
showComponent(true);
info = c.getViewerCursorLocation();
assertEquals(addr, convertToAddr(info));
}
private Address getAddr(long offset) {
return program.getMinAddress().getNewAddress(offset);
}
private Address convertToAddr(ByteBlockInfo info) {
return ((ProgramByteBlockSet) plugin.getProvider().getByteBlockSet()).getAddress(
info.getBlock(), info.getOffset());
}
private FieldLocation getFieldLocation(Address addr) {
ByteViewerComponent c = panel.getCurrentComponent();
ProgramByteBlockSet blockset = (ProgramByteBlockSet) plugin.getProvider().getByteBlockSet();
ByteBlockInfo bbInfo = blockset.getByteBlockInfo(addr);
return c.getFieldLocation(bbInfo.getBlock(), bbInfo.getOffset());
}
private void showComponent(final boolean visible) throws Exception {
SwingUtilities.invokeAndWait(new Runnable() {
@Override
public void run() {
tool.showComponentProvider(plugin.getProvider(), visible);
}
});
private void showComponent(boolean visible) throws Exception {
SwingUtilities.invokeAndWait(() -> tool.showComponentProvider(provider, visible));
}
}

View File

@@ -0,0 +1,162 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.byteviewer;
import static org.junit.Assert.*;
import java.nio.charset.StandardCharsets;
import java.util.List;
import org.junit.Test;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
import ghidra.app.plugin.core.navigation.NavigationHistoryPlugin;
import ghidra.app.plugin.core.navigation.NextPrevAddressPlugin;
import ghidra.framework.plugintool.Plugin;
import ghidra.program.database.ProgramBuilder;
import ghidra.program.model.listing.Program;
import ghidra.util.charset.CharsetInfoManager;
/**
* Test for "chars" format
*/
public class ByteViewerPluginCharFormatTest extends AbstractByteViewerPluginTest {
@Override
protected List<Class<? extends Plugin>> getDefaultPlugins() {
return List.of(NavigationHistoryPlugin.class, NextPrevAddressPlugin.class,
CodeBrowserPlugin.class);
}
@Override
protected Program buildProgram() throws Exception {
ProgramBuilder builder = new ProgramBuilder("notepad", ProgramBuilder._TOY);
builder.createMemory("test2", "0x1001000", 0x2000);
builder.setBytes("0x1001000", "abc".getBytes(StandardCharsets.US_ASCII));
builder.setBytes("0x1001100", "\u6211\u7684\u6c23\u588a\u8239\u88dd\u6eff\u4e86\u9c3b\u9b5a"
.getBytes(StandardCharsets.UTF_16BE));
Program p = builder.getProgram();
p.clearUndo();
return p;
}
@Test
public void testASCII() throws Exception {
loadViews("Chars");
ByteViewerComponent c = setView("Chars");
goTo(addr(0x1001000));
assertEquals("a", getField(c, addr(0x1001000)).getText());
assertEquals("b", getField(c, addr(0x1001001)).getText());
assertEquals("c", getField(c, addr(0x1001002)).getText());
assertEquals(".", getField(c, addr(0x1001003)).getText());
}
@Test
public void testIBM037() throws Exception {
// IBM code page 37 (EBCDIC, completely unrelated to ASCII)
loadViews("Chars");
provider.setCharsetInfo(CharsetInfoManager.getInstance().get("IBM037"));
ByteViewerComponent c = setView("Chars");
goTo(addr(0x1001000));
// instead of 'a', 'b', 'c', we get '/', 'A-accent', 'A-otheraccent'
assertEquals("/", getField(c, addr(0x1001000)).getText());
assertEquals("\u00c2", getField(c, addr(0x1001001)).getText());
assertEquals("\u00c4", getField(c, addr(0x1001002)).getText());
assertEquals(".", getField(c, addr(0x1001003)).getText());
}
@Test
public void testUTF16_alignment_off() throws Exception {
ByteViewerConfigOptions configOptions = provider.getConfigOptions().clone();
configOptions.setUseCharAlignment(false);
provider.updateConfigOptions(configOptions, null);
loadViews("Chars");
provider.setCharsetInfo(CharsetInfoManager.getInstance().get("UTF-16"));
ByteViewerComponent c = setView("Chars");
goTo(addr(0x1001100));
// overlapping UTF-16 chars [62 11] [11 76]
assertEquals("\u6211", getField(c, addr(0x1001100)).getText());
assertEquals("\u1176", getField(c, addr(0x1001101)).getText());
assertEquals(16, c.getNumberOfFields()); // should have 16 (overlapping) chars per row
}
@Test
public void testUTF16_alignment_on() throws Exception {
ByteViewerConfigOptions configOptions = provider.getConfigOptions().clone();
configOptions.setUseCharAlignment(true);
provider.updateConfigOptions(configOptions, null);
loadViews("Chars");
provider.setCharsetInfo(CharsetInfoManager.getInstance().get("UTF-16"));
ByteViewerComponent c = setView("Chars");
goTo(addr(0x1001100));
assertEquals("\u6211", getField(c, addr(0x1001100)).getText());
assertEquals("\u6211", getField(c, addr(0x1001101)).getText()); // still same value because addr is offcut
assertEquals("\u7684", getField(c, addr(0x1001102)).getText());
assertEquals(16 / 2, c.getNumberOfFields()); // should only have 8 chars per row instead of 16
}
@Test
public void testUTF32_badval() throws Exception {
ByteViewerConfigOptions configOptions = provider.getConfigOptions().clone();
configOptions.setUseCharAlignment(true);
provider.updateConfigOptions(configOptions, null);
loadViews("Chars");
provider.setCharsetInfo(CharsetInfoManager.getInstance().get("UTF-32"));
ByteViewerComponent c = setView("Chars");
goTo(addr(0x1001000));
assertEquals(".", getField(c, addr(0x1001100)).getText());
}
@Test
public void testWideChars() throws Exception {
loadViews("Chars");
provider.setCompactChars(true);
provider.setCharsetInfo(CharsetInfoManager.getInstance().get("UTF-16"));
ByteViewerComponent c = setView("Chars");
goTo(addr(0x1001100));
ByteField field = getField(c, addr(0x1001100));
int compactWidth = field.getWidth();
provider.setCompactChars(false);
field = getField(c, addr(0x1001100));
int wideWidth = field.getWidth();
assertTrue(wideWidth == compactWidth * 2); // we know wide mode is 2x compact mode
}
}

View File

@@ -17,80 +17,50 @@ package ghidra.app.plugin.core.byteviewer;
import static org.junit.Assert.*;
import java.awt.*;
import java.awt.Color;
import java.awt.event.KeyEvent;
import java.math.BigInteger;
import java.util.Date;
import java.util.Map;
import java.util.List;
import javax.swing.*;
import javax.swing.JScrollBar;
import org.junit.*;
import org.junit.Test;
import docking.DefaultActionContext;
import docking.action.DockingActionIf;
import docking.action.ToggleDockingAction;
import docking.widgets.fieldpanel.support.FieldLocation;
import docking.widgets.indexedscrollpane.IndexedScrollPane;
import generic.test.TestUtils;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
import ghidra.app.plugin.core.format.*;
import ghidra.app.plugin.core.navigation.NavigationHistoryPlugin;
import ghidra.app.plugin.core.navigation.NextPrevAddressPlugin;
import ghidra.app.services.GoToService;
import ghidra.app.services.ProgramManager;
import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.plugintool.Plugin;
import ghidra.program.database.ProgramBuilder;
import ghidra.program.database.ProgramDB;
import ghidra.program.model.address.*;
import ghidra.program.model.data.ByteDataType;
import ghidra.program.model.data.StructureDataType;
import ghidra.program.model.listing.Listing;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.program.util.ProgramLocation;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
import ghidra.test.TestEnv;
/**
* Test for byte viewer formats.
*/
public class ByteViewerPluginFormatsTest extends AbstractGhidraHeadedIntegrationTest {
public class ByteViewerPluginFormatsTest extends AbstractByteViewerPluginTest {
private TestEnv env;
private PluginTool tool;
private Program program;
private Memory memory;
private Listing listing;
private ByteViewerPlugin plugin;
private ByteViewerPanel panel;
private ByteViewerComponentProvider provider;
@Before
public void setUp() throws Exception {
env = new TestEnv();
tool = env.getTool();
tool.addPlugin(NavigationHistoryPlugin.class.getName());
tool.addPlugin(NextPrevAddressPlugin.class.getName());
tool.addPlugin(CodeBrowserPlugin.class.getName());
tool.addPlugin(ByteViewerPlugin.class.getName());
plugin = env.getPlugin(ByteViewerPlugin.class);
provider =
(ByteViewerComponentProvider) TestUtils.getInstanceField("connectedProvider", plugin);
tool.showComponentProvider(provider, true);
program = buildNotepad();
memory = program.getMemory();
listing = program.getListing();
ProgramManager pm = tool.getService(ProgramManager.class);
pm.openProgram(program.getDomainFile());
panel = provider.getByteViewerPanel();
waitForSwing();
@Override
protected List<Class<? extends Plugin>> getDefaultPlugins() {
return List.of(NavigationHistoryPlugin.class, NextPrevAddressPlugin.class,
CodeBrowserPlugin.class);
}
private Program buildNotepad() throws Exception {
@Override
protected Program buildProgram() throws Exception {
ProgramBuilder builder = new ProgramBuilder("notepad", ProgramBuilder._TOY);
builder.createMemory("test2", "0x1001000", 0x2000);
Program p = builder.getProgram();
@@ -105,11 +75,6 @@ public class ByteViewerPluginFormatsTest extends AbstractGhidraHeadedIntegration
return builder.getProgram();
}
@After
public void tearDown() throws Exception {
env.release(program);
env.dispose();
}
@Test
public void testInsertionFieldHex() throws Exception {
@@ -118,7 +83,7 @@ public class ByteViewerPluginFormatsTest extends AbstractGhidraHeadedIntegration
// move the cursor to the right; the insertion field should not update
runSwing(() -> {
FieldLocation loc = getFieldLocation(getAddr(0x01001000));
FieldLocation loc = getFieldLocation(addr(0x01001000));
ByteViewerComponent c = panel.getCurrentComponent();
c.setCursorPosition(loc.getIndex(), loc.getFieldNum(), 0, 0);
c.cursorRight();
@@ -135,7 +100,7 @@ public class ByteViewerPluginFormatsTest extends AbstractGhidraHeadedIntegration
// TestUtils.getInstanceField("updateManager", provider);
// waitForSwingUpdateManager( updateManager );
waitForSwing();
assertEquals(getAddr(0x01001001).toString(), findLabelStr(panel, "Insertion"));
assertEquals(addr(0x01001001).toString(), findLabelStr(panel, "Insertion"));
// move the cursor to the left; the insertion field should have
// been decremented by one
@@ -143,7 +108,7 @@ public class ByteViewerPluginFormatsTest extends AbstractGhidraHeadedIntegration
ByteViewerComponent c = panel.getCurrentComponent();
c.cursorLeft();
});
assertEquals(getAddr(0x01001000).toString(), findLabelStr(panel, "Insertion"));
assertEquals(addr(0x01001000).toString(), findLabelStr(panel, "Insertion"));
// move the cursor to the left; the insertion field should not change
runSwing(() -> {
@@ -164,7 +129,7 @@ public class ByteViewerPluginFormatsTest extends AbstractGhidraHeadedIntegration
// move the cursor to the right two times; the insertion field should not update
runSwing(() -> {
FieldLocation loc = getFieldLocation(getAddr(0x01001000));
FieldLocation loc = getFieldLocation(addr(0x01001000));
ByteViewerComponent currentComponent = panel.getCurrentComponent();
currentComponent.setCursorPosition(loc.getIndex(), loc.getFieldNum(), 0, 0);
currentComponent.cursorRight();
@@ -177,7 +142,7 @@ public class ByteViewerPluginFormatsTest extends AbstractGhidraHeadedIntegration
ByteViewerComponent currentComponent = panel.getCurrentComponent();
currentComponent.cursorRight();
});
assertEquals(getAddr(0x01001001).toString(), findLabelStr(panel, "Insertion"));
assertEquals(addr(0x01001001).toString(), findLabelStr(panel, "Insertion"));
// move the cursor to the left; the insertion field should have
// been decremented by one
@@ -185,7 +150,7 @@ public class ByteViewerPluginFormatsTest extends AbstractGhidraHeadedIntegration
ByteViewerComponent currentComponent = panel.getCurrentComponent();
currentComponent.cursorLeft();
});
assertEquals(getAddr(0x01001000).toString(), findLabelStr(panel, "Insertion"));
assertEquals(addr(0x01001000).toString(), findLabelStr(panel, "Insertion"));
// move the cursor to the left two times; the insertion field should not change
runSwing(() -> {
@@ -200,26 +165,26 @@ public class ByteViewerPluginFormatsTest extends AbstractGhidraHeadedIntegration
public void testInsertionFieldAscii() throws Exception {
env.showTool();
addViews();
ByteViewerComponent c = findComponent(panel, "Ascii");
ByteViewerComponent c = findComponent(panel, "Chars");
panel.setCurrentView(c);
String insertionStr = findLabelStr(panel, "Insertion");
// move the cursor to the right; the insertion field should update by one
runSwing(() -> {
FieldLocation loc = getFieldLocation(getAddr(0x01001000));
FieldLocation loc = getFieldLocation(addr(0x01001000));
ByteViewerComponent currentComponent = panel.getCurrentComponent();
currentComponent.setCursorPosition(loc.getIndex(), loc.getFieldNum(), 0, 0);
currentComponent.cursorRight();
});
assertEquals(getAddr(0x01001001).toString(), findLabelStr(panel, "Insertion"));
assertEquals(addr(0x01001001).toString(), findLabelStr(panel, "Insertion"));
// move the cursor to the right; the insertion field should update by one
runSwing(() -> {
ByteViewerComponent currentComponent = panel.getCurrentComponent();
currentComponent.cursorRight();
});
assertEquals(getAddr(0x01001002).toString(), findLabelStr(panel, "Insertion"));
assertEquals(addr(0x01001002).toString(), findLabelStr(panel, "Insertion"));
// move the cursor to the left; the insertion field should have
// been decremented by one
@@ -227,7 +192,7 @@ public class ByteViewerPluginFormatsTest extends AbstractGhidraHeadedIntegration
ByteViewerComponent currentComponent = panel.getCurrentComponent();
currentComponent.cursorLeft();
});
assertEquals(getAddr(0x01001001).toString(), findLabelStr(panel, "Insertion"));
assertEquals(addr(0x01001001).toString(), findLabelStr(panel, "Insertion"));
// move the cursor to the left; the insertion field should update
runSwing(() -> {
@@ -248,7 +213,7 @@ public class ByteViewerPluginFormatsTest extends AbstractGhidraHeadedIntegration
assertEquals(8, c.getNumberOfFields());
assertEquals(2, c.getDataModel().getUnitByteSize());
final FieldLocation loc = getFieldLocation(getAddr(0x01001000));
final FieldLocation loc = getFieldLocation(addr(0x01001000));
runSwing(() -> {
ByteViewerComponent currentComponent = panel.getCurrentComponent();
currentComponent.setCursorPosition(loc.getIndex(), loc.getFieldNum(), 0, 0);
@@ -264,12 +229,11 @@ public class ByteViewerPluginFormatsTest extends AbstractGhidraHeadedIntegration
env.showTool();
addViews();
final ByteViewerComponent c = findComponent(panel, "Ascii");
final ByteViewerComponent c = findComponent(panel, "Chars");
panel.setCurrentView(c);
final ToggleDockingAction action =
(ToggleDockingAction) getAction(plugin, "Enable/Disable Byteviewer Editing");
final FieldLocation loc = getFieldLocation(getAddr(0x01001000));
ToggleDockingAction action = provider.getEditModeAction();
final FieldLocation loc = getFieldLocation(addr(0x01001000));
runSwing(() -> {
ByteViewerComponent currentComponent = panel.getCurrentComponent();
currentComponent.setCursorPosition(loc.getIndex(), loc.getFieldNum(), 0, 0);
@@ -287,7 +251,7 @@ public class ByteViewerPluginFormatsTest extends AbstractGhidraHeadedIntegration
runSwing(() -> {
ProgramByteBlockSet blockset =
(ProgramByteBlockSet) plugin.getProvider().getByteBlockSet();
ByteBlockInfo bbInfo = blockset.getByteBlockInfo(getAddr(0x01001000));
ByteBlockInfo bbInfo = blockset.getByteBlockInfo(addr(0x01001000));
FieldLocation l = hexComp.getFieldLocation(bbInfo.getBlock(), bbInfo.getOffset());
hexComp.setCursorPosition(l.getIndex(), l.getFieldNum(), 0, 0);
});
@@ -307,7 +271,7 @@ public class ByteViewerPluginFormatsTest extends AbstractGhidraHeadedIntegration
assertEquals(4, c.getNumberOfFields());
assertEquals(4, c.getDataModel().getUnitByteSize());
final FieldLocation loc = getFieldLocation(getAddr(0x01001000));
final FieldLocation loc = getFieldLocation(addr(0x01001000));
runSwing(() -> {
ByteViewerComponent currentComponent = panel.getCurrentComponent();
currentComponent.setCursorPosition(loc.getIndex(), loc.getFieldNum(), 0, 0);
@@ -323,12 +287,11 @@ public class ByteViewerPluginFormatsTest extends AbstractGhidraHeadedIntegration
env.showTool();
addViews();
final ByteViewerComponent c = findComponent(panel, "Ascii");
final ByteViewerComponent c = findComponent(panel, "Chars");
panel.setCurrentView(c);
final ToggleDockingAction action =
(ToggleDockingAction) getAction(plugin, "Enable/Disable Byteviewer Editing");
final FieldLocation loc = getFieldLocation(getAddr(0x01001000));
ToggleDockingAction action = provider.getEditModeAction();
final FieldLocation loc = getFieldLocation(addr(0x01001000));
runSwing(() -> {
ByteViewerComponent currentComponent = panel.getCurrentComponent();
currentComponent.setCursorPosition(loc.getIndex(), loc.getFieldNum(), 0, 0);
@@ -346,7 +309,7 @@ public class ByteViewerPluginFormatsTest extends AbstractGhidraHeadedIntegration
runSwing(() -> {
ProgramByteBlockSet blockset =
(ProgramByteBlockSet) plugin.getProvider().getByteBlockSet();
ByteBlockInfo bbInfo = blockset.getByteBlockInfo(getAddr(0x01001000));
ByteBlockInfo bbInfo = blockset.getByteBlockInfo(addr(0x01001000));
FieldLocation l = hexComp.getFieldLocation(bbInfo.getBlock(), bbInfo.getOffset());
hexComp.setCursorPosition(l.getIndex(), l.getFieldNum(), 0, 0);
});
@@ -366,7 +329,7 @@ public class ByteViewerPluginFormatsTest extends AbstractGhidraHeadedIntegration
assertEquals(2, c.getNumberOfFields());
assertEquals(8, c.getDataModel().getUnitByteSize());
final FieldLocation loc = getFieldLocation(getAddr(0x01001000));
final FieldLocation loc = getFieldLocation(addr(0x01001000));
runSwing(() -> {
ByteViewerComponent currentComponent = panel.getCurrentComponent();
currentComponent.setCursorPosition(loc.getIndex(), loc.getFieldNum(), 0, 0);
@@ -382,12 +345,11 @@ public class ByteViewerPluginFormatsTest extends AbstractGhidraHeadedIntegration
env.showTool();
addViews();
final ByteViewerComponent c = findComponent(panel, "Ascii");
final ByteViewerComponent c = findComponent(panel, "Chars");
panel.setCurrentView(c);
final ToggleDockingAction action =
(ToggleDockingAction) getAction(plugin, "Enable/Disable Byteviewer Editing");
final FieldLocation loc = getFieldLocation(getAddr(0x01001000));
ToggleDockingAction action = provider.getEditModeAction();
final FieldLocation loc = getFieldLocation(addr(0x01001000));
runSwing(() -> {
ByteViewerComponent currentComponent = panel.getCurrentComponent();
currentComponent.setCursorPosition(loc.getIndex(), loc.getFieldNum(), 0, 0);
@@ -405,7 +367,7 @@ public class ByteViewerPluginFormatsTest extends AbstractGhidraHeadedIntegration
runSwing(() -> {
ProgramByteBlockSet blockset =
(ProgramByteBlockSet) plugin.getProvider().getByteBlockSet();
ByteBlockInfo bbInfo = blockset.getByteBlockInfo(getAddr(0x01001000));
ByteBlockInfo bbInfo = blockset.getByteBlockInfo(addr(0x01001000));
FieldLocation l = hexComp.getFieldLocation(bbInfo.getBlock(), bbInfo.getOffset());
hexComp.setCursorPosition(l.getIndex(), l.getFieldNum(), 0, 0);
});
@@ -425,7 +387,7 @@ public class ByteViewerPluginFormatsTest extends AbstractGhidraHeadedIntegration
assertEquals(1, c.getNumberOfFields());
assertEquals(16, c.getDataModel().getUnitByteSize());
final FieldLocation loc = getFieldLocation(getAddr(0x01001000));
final FieldLocation loc = getFieldLocation(addr(0x01001000));
runSwing(() -> {
ByteViewerComponent currentComponent = panel.getCurrentComponent();
currentComponent.setCursorPosition(loc.getIndex(), loc.getFieldNum(), 0, 0);
@@ -441,12 +403,11 @@ public class ByteViewerPluginFormatsTest extends AbstractGhidraHeadedIntegration
env.showTool();
addViews();
final ByteViewerComponent c = findComponent(panel, "Ascii");
final ByteViewerComponent c = findComponent(panel, "Chars");
panel.setCurrentView(c);
final ToggleDockingAction action =
(ToggleDockingAction) getAction(plugin, "Enable/Disable Byteviewer Editing");
final FieldLocation loc = getFieldLocation(getAddr(0x01001000));
ToggleDockingAction action = provider.getEditModeAction();
final FieldLocation loc = getFieldLocation(addr(0x01001000));
runSwing(() -> {
ByteViewerComponent currentComponent = panel.getCurrentComponent();
currentComponent.setCursorPosition(loc.getIndex(), loc.getFieldNum(), 0, 0);
@@ -464,7 +425,7 @@ public class ByteViewerPluginFormatsTest extends AbstractGhidraHeadedIntegration
runSwing(() -> {
ProgramByteBlockSet blockset =
(ProgramByteBlockSet) plugin.getProvider().getByteBlockSet();
ByteBlockInfo bbInfo = blockset.getByteBlockInfo(getAddr(0x01001000));
ByteBlockInfo bbInfo = blockset.getByteBlockInfo(addr(0x01001000));
FieldLocation l = hexComp.getFieldLocation(bbInfo.getBlock(), bbInfo.getOffset());
hexComp.setCursorPosition(l.getIndex(), l.getFieldNum(), 0, 0);
});
@@ -479,7 +440,7 @@ public class ByteViewerPluginFormatsTest extends AbstractGhidraHeadedIntegration
env.showTool();
runSwing(() -> {
FieldLocation loc = getFieldLocation(getAddr(0x01001000));
FieldLocation loc = getFieldLocation(addr(0x01001000));
ByteViewerComponent c = panel.getCurrentComponent();
c.setCursorPosition(loc.getIndex(), loc.getFieldNum(), loc.getRow(), loc.getCol());
});
@@ -506,13 +467,13 @@ public class ByteViewerPluginFormatsTest extends AbstractGhidraHeadedIntegration
ByteViewerComponent currentComponent = panel.getCurrentComponent();
ProgramByteBlockSet blockset =
(ProgramByteBlockSet) plugin.getProvider().getByteBlockSet();
ByteBlockInfo bbInfo = blockset.getByteBlockInfo(getAddr(0x01001003));
ByteBlockInfo bbInfo = blockset.getByteBlockInfo(addr(0x01001003));
currentComponent.setViewerCursorLocation(bbInfo.getBlock(), bbInfo.getOffset(),
bbInfo.getColumn());
});
waitForSwing();
FieldLocation loc = getFieldLocation(getAddr(0x01001003));
FieldLocation loc = getFieldLocation(addr(0x01001003));
assertEquals(11, c.getCurrentField().getNumCols(loc.getRow()));
// verify that no edits can be done in Integer format since it
@@ -528,7 +489,7 @@ public class ByteViewerPluginFormatsTest extends AbstractGhidraHeadedIntegration
env.showTool();
runSwing(() -> {
FieldLocation loc = getFieldLocation(getAddr(0x01001000));
FieldLocation loc = getFieldLocation(addr(0x01001000));
ByteViewerComponent c = panel.getCurrentComponent();
c.setCursorPosition(loc.getIndex(), loc.getFieldNum(), loc.getRow(), loc.getCol());
});
@@ -555,13 +516,13 @@ public class ByteViewerPluginFormatsTest extends AbstractGhidraHeadedIntegration
ByteViewerComponent currentComponent = panel.getCurrentComponent();
ProgramByteBlockSet blockset =
(ProgramByteBlockSet) plugin.getProvider().getByteBlockSet();
ByteBlockInfo bbInfo = blockset.getByteBlockInfo(getAddr(0x01001003));
ByteBlockInfo bbInfo = blockset.getByteBlockInfo(addr(0x01001003));
currentComponent.setViewerCursorLocation(bbInfo.getBlock(), bbInfo.getOffset(),
bbInfo.getColumn());
});
waitForSwing();
FieldLocation loc = getFieldLocation(getAddr(0x01001003));
FieldLocation loc = getFieldLocation(addr(0x01001003));
assertEquals(3, c.getCurrentField().getNumCols(loc.getRow()));
// verify that no edits can be done in Integer format since it
@@ -577,19 +538,18 @@ public class ByteViewerPluginFormatsTest extends AbstractGhidraHeadedIntegration
// verify that changed bytes are rendered in red in the Integer view
env.showTool();
ByteViewerOptionsDialog dialog = launchByteViewerOptions();
setViewSelected(dialog, "Ascii", true);
setViewSelected(dialog, "Chars", true);
setViewSelected(dialog, "Octal", true);
setViewSelected(dialog, "Hex Integer", true);
setViewSelected(dialog, "Integer", true);
pressButtonByText(dialog.getComponent(), "OK");
waitForSwing();
final ByteViewerComponent c = findComponent(panel, "Ascii");
final ByteViewerComponent c = findComponent(panel, "Chars");
panel.setCurrentView(c);
final ToggleDockingAction action =
(ToggleDockingAction) getAction(plugin, "Enable/Disable Byteviewer Editing");
final FieldLocation loc = getFieldLocation(getAddr(0x01001000));
ToggleDockingAction action = provider.getEditModeAction();
final FieldLocation loc = getFieldLocation(addr(0x01001000));
runSwing(() -> {
ByteViewerComponent currentComponent = panel.getCurrentComponent();
currentComponent.setCursorPosition(loc.getIndex(), loc.getFieldNum(), 0, 0);
@@ -608,7 +568,7 @@ public class ByteViewerPluginFormatsTest extends AbstractGhidraHeadedIntegration
runSwing(() -> {
ProgramByteBlockSet blockset =
(ProgramByteBlockSet) plugin.getProvider().getByteBlockSet();
ByteBlockInfo bbInfo = blockset.getByteBlockInfo(getAddr(0x01001000));
ByteBlockInfo bbInfo = blockset.getByteBlockInfo(addr(0x01001000));
FieldLocation l = intComp.getFieldLocation(bbInfo.getBlock(), bbInfo.getOffset());
intComp.setCursorPosition(l.getIndex(), l.getFieldNum(), 0, 0);
});
@@ -622,7 +582,7 @@ public class ByteViewerPluginFormatsTest extends AbstractGhidraHeadedIntegration
env.showTool();
runSwing(() -> {
FieldLocation loc = getFieldLocation(getAddr(0x01001000));
FieldLocation loc = getFieldLocation(addr(0x01001000));
ByteViewerComponent c = panel.getCurrentComponent();
c.setCursorPosition(loc.getIndex(), loc.getFieldNum(), loc.getRow(), loc.getCol());
});
@@ -633,10 +593,10 @@ public class ByteViewerPluginFormatsTest extends AbstractGhidraHeadedIntegration
final ByteViewerComponent c = findComponent(panel, "Integer");
panel.setCurrentView(c);
int v = program.getMemory().getInt(getAddr(0x01001000));
int v = program.getMemory().getInt(addr(0x01001000));
runSwing(() -> {
FieldLocation loc = getFieldLocation(getAddr(0x01001000));
DockingActionIf action = getAction(plugin, "Enable/Disable Byteviewer Editing");
FieldLocation loc = getFieldLocation(addr(0x01001000));
ToggleDockingAction action = provider.getEditModeAction();
ByteViewerComponent currentComponent = panel.getCurrentComponent();
currentComponent.setCursorPosition(loc.getIndex(), loc.getFieldNum(), 0, 0);
@@ -649,7 +609,7 @@ public class ByteViewerPluginFormatsTest extends AbstractGhidraHeadedIntegration
});
program.flushEvents();
// verify that no changes were made
assertEquals(v, program.getMemory().getInt(getAddr(0x01001000)));
assertEquals(v, program.getMemory().getInt(addr(0x01001000)));
}
@Test
@@ -782,12 +742,12 @@ public class ByteViewerPluginFormatsTest extends AbstractGhidraHeadedIntegration
panel.setCurrentView(c);
GoToService goToService = tool.getService(GoToService.class);
goToService.goTo(new ProgramLocation(program, getAddr(01001530)));
goToService.goTo(new ProgramLocation(program, addr(01001530)));
waitForSwing();
int transactionID = program.startTransaction("test");
Address addr = getAddr(0x01001530);
Address addr = addr(0x01001530);
for (int i = 0; i < 5; i++) {
listing.createData(addr, new ByteDataType(), 1);
addr = addr.add(1);
@@ -797,7 +757,7 @@ public class ByteViewerPluginFormatsTest extends AbstractGhidraHeadedIntegration
program.flushEvents();
waitForSwing();
addr = getAddr(0x01001530);
addr = addr(0x01001530);
for (int i = 0; i < 5; i++) {
FieldLocation loc = getFieldLocation(addr);
ByteField field = c.getField(loc.getIndex(), loc.getFieldNum());
@@ -821,12 +781,12 @@ public class ByteViewerPluginFormatsTest extends AbstractGhidraHeadedIntegration
panel.setCurrentView(c);
GoToService goToService = tool.getService(GoToService.class);
goToService.goTo(new ProgramLocation(program, getAddr(01001530)));
goToService.goTo(new ProgramLocation(program, addr(01001530)));
waitForSwing();
int transactionID = program.startTransaction("test");
Address addr = getAddr(0x01001530);
Address addr = addr(0x01001530);
StructureDataType dt = new StructureDataType("test", 0);
for (int i = 0; i < 7; i++) {
dt.add(new ByteDataType());
@@ -837,7 +797,7 @@ public class ByteViewerPluginFormatsTest extends AbstractGhidraHeadedIntegration
program.flushEvents();
waitForSwing();
addr = getAddr(0x01001530);
addr = addr(0x01001530);
for (int i = 0; i < dt.getLength(); i++) {
FieldLocation loc = getFieldLocation(addr);
ByteField field = c.getField(loc.getIndex(), loc.getFieldNum());
@@ -910,100 +870,7 @@ public class ByteViewerPluginFormatsTest extends AbstractGhidraHeadedIntegration
return null;
}
private Address getAddr(long offset) {
return getAddr(program, offset);
}
private Address getAddr(Program p, long offset) {
return p.getMinAddress().getNewAddress(offset);
}
private FieldLocation getFieldLocation(Address addr) {
ByteViewerComponent c = panel.getCurrentComponent();
ProgramByteBlockSet blockset = (ProgramByteBlockSet) plugin.getProvider().getByteBlockSet();
ByteBlockInfo bbInfo = blockset.getByteBlockInfo(addr);
return c.getFieldLocation(bbInfo.getBlock(), bbInfo.getOffset());
}
private String findLabelStr(Container container, String name) {
Component[] c = container.getComponents();
for (Component element : c) {
if (element instanceof JLabel) {
if (name.equals(element.getName())) {
return ((JLabel) element).getText();
}
}
if (element instanceof Container) {
String str = findLabelStr((Container) element, name);
if (str != null) {
return str;
}
}
}
return null;
}
private ByteViewerComponent findComponent(Container container, String name) {
Component[] c = container.getComponents();
for (Component element : c) {
if (element instanceof ByteViewerComponent) {
if (((ByteViewerComponent) element).getDataModel().getName().equals(name)) {
return (ByteViewerComponent) element;
}
}
else if (element instanceof Container) {
ByteViewerComponent bvc = findComponent((Container) element, name);
if (bvc != null) {
return bvc;
}
}
}
return null;
}
private Container findContainer(Container parent, Class<?> theClass) {
Component[] c = parent.getComponents();
for (Component element : c) {
if (element.getClass() == theClass) {
return (Container) element;
}
if (element instanceof Container) {
Container container = findContainer((Container) element, theClass);
if (container != null) {
return container;
}
}
}
return null;
}
private void addViews() throws Exception {
ByteViewerOptionsDialog dialog = launchByteViewerOptions();
setViewSelected(dialog, "Ascii", true);
setViewSelected(dialog, "Octal", true);
setViewSelected(dialog, "Hex Short", true);
setViewSelected(dialog, "Hex Integer", true);
setViewSelected(dialog, "Hex Long", true);
setViewSelected(dialog, "Hex Long Long", true);
pressButtonByText(dialog.getComponent(), "OK");
waitForSwing();
}
private void setViewSelected(ByteViewerOptionsDialog dialog, String viewName,
boolean selected) {
Map<?, ?> checkboxMap = (Map<?, ?>) getInstanceField("checkboxMap", dialog);
JCheckBox checkbox = (JCheckBox) checkboxMap.get(viewName);
checkbox.setSelected(selected);
}
private ByteViewerOptionsDialog launchByteViewerOptions() {
final DockingActionIf action = getAction(plugin, "Byte Viewer Options");
assertTrue(action.isEnabled());
runSwing(() -> action.actionPerformed(new DefaultActionContext()), false);
waitForSwing();
ByteViewerOptionsDialog d = waitForDialogComponent(ByteViewerOptionsDialog.class);
return d;
loadViews("Chars", "Octal", "Hex Short", "Hex Integer", "Hex Long", "Hex Long Long");
}
}

View File

@@ -19,8 +19,8 @@ import java.nio.charset.Charset;
import java.util.List;
import ghidra.framework.options.Options;
import ghidra.program.model.data.CharsetInfo;
import ghidra.util.HelpLocation;
import ghidra.util.charset.CharsetInfoManager;
/**
* Options used while reading a PDB ({@link AbstractPdb}) that control various aspects. These
@@ -39,7 +39,7 @@ public class PdbReaderOptions extends Exception {
private static final String OPTION_DESCRIPTION_ONE_BYTE_CHARSET_NAME =
"Charset used for processing of one-byte (or multi) encoded Strings: " +
PdbReaderOptions.getOneByteCharsetNames();
private static final String DEFAULT_ONE_BYTE_CHARSET_NAME = CharsetInfo.UTF8;
private static final String DEFAULT_ONE_BYTE_CHARSET_NAME = CharsetInfoManager.UTF8;
private String oneByteCharsetName;
// Sets the wchar_t Charset to be used for PDB processing.
@@ -49,14 +49,14 @@ public class PdbReaderOptions extends Exception {
private static final String OPTION_DESCRIPTION_WCHAR_CHARSET_NAME =
"Charset used for processing of wchar_t encoded Strings: " +
PdbReaderOptions.getTwoByteCharsetNames();
private static final String DEFAULT_TWO_BYTE_CHARSET_NAME = CharsetInfo.UTF16;
private static final String DEFAULT_TWO_BYTE_CHARSET_NAME = CharsetInfoManager.UTF16;
private String wideCharCharsetName;
//==============================================================================================
private static List<String> oneByteCharsetNames =
CharsetInfo.getInstance().getCharsetNamesWithCharSize(1);
CharsetInfoManager.getInstance().getCharsetNamesWithCharSize(1);
private static List<String> twoByteCharsetNames =
CharsetInfo.getInstance().getCharsetNamesWithCharSize(2);
CharsetInfoManager.getInstance().getCharsetNamesWithCharSize(2);
private Charset oneByteCharset;
private Charset wideCharset;

View File

@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -15,12 +15,11 @@
*/
package docking.widgets.spinner;
import java.util.ArrayList;
import java.util.List;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JSpinner;
import javax.swing.SpinnerNumberModel;
@@ -29,12 +28,13 @@ import javax.swing.event.*;
import docking.widgets.textfield.IntegerTextField;
/**
* Creates a component for editing Integer values using an {@link IntegerTextField} and a {@link JSpinner}.
* Creates a component for editing Integer values using an {@link IntegerTextField} and a
* {@link JSpinner}.
*/
public class IntegerSpinner {
private final JSpinner spinner;
private final IntegerTextField integerTextField;
protected final JSpinner spinner;
protected final IntegerTextField integerTextField;
private List<ChangeListener> changeListeners = new ArrayList<>();
@@ -51,6 +51,7 @@ public class IntegerSpinner {
* Creates a new IntegerSpinner using the given spinner model.
*
* @param spinnerModel the spinner model to use in the JSpinner.
* @param columns number of columns to display in in the JTextField
*/
public IntegerSpinner(SpinnerNumberModel spinnerModel, int columns) {
@@ -66,6 +67,14 @@ public class IntegerSpinner {
spinner.setEditor(integerTextField.getComponent());
spinnerModel.addChangeListener(e -> {
Number newMaxNum = (Number) spinnerModel.getMaximum();
BigInteger newMax =
newMaxNum != null ? BigInteger.valueOf(newMaxNum.longValue()) : null;
BigInteger oldMax = integerTextField.getMaxValue();
if (newMax != null && !newMax.equals(oldMax) || (newMax == null && oldMax != null)) {
integerTextField.setMaxValue(newMax);
}
Number newVal = (Number) spinnerModel.getValue();
integerTextField.setValue(newVal.longValue());
});
@@ -120,7 +129,6 @@ public class IntegerSpinner {
}
spinnerModel.setValue(value.longValue());
});
}
/**

View File

@@ -7,7 +7,7 @@
Module.manifest||GHIDRA||||END|
README.md||GHIDRA||||END|
data/ExtensionPoint.manifest||GHIDRA||||END|
data/charset_info.xml||GHIDRA||||END|
data/charset_info.json||GHIDRA||||END|
data/languages/compiler_spec.rxg||GHIDRA||||END|
data/languages/format_opinions.rxg||GHIDRA||||END|
data/languages/language_common.rxg||GHIDRA||||END|

File diff suppressed because it is too large Load Diff

View File

@@ -1,19 +0,0 @@
<charsetinfo>
<!-- <charset name="java_charset_name" charSize="numBytesPerChar_ie_1_2_4" /> -->
<!-- name is the exact name of the java.nio.charset.Charset. -->
<!-- charSize is the number of bytes that are used to produce a character.
For charsets like UTF-8 or Big5, etc that consume a variable (1-2) number of bytes per
character, specify a charSize="1", as this value is mainly used by Ghidra to find the
null-terminator at the end of a string.
-->
<!-- This file contains extra information Ghidra needs about java Charsets before they can be used. -->
<!-- These Charset implementions need to be present in the java JVM before Ghidra can use them. -->
<!-- Charsets defined in this file will be shown to the user in a UI list in the order listed below. -->
<!-- User added charsets should listed below. Only Charsets that have a charSize -->
<!-- other than 1 byte need to be defined here. -->
<!-- <charset name="FROBLE24" charSize="3" comment="3 byte fictional charset"/> -->
</charsetinfo>

View File

@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -16,13 +16,13 @@
package ghidra;
import ghidra.framework.ModuleInitializer;
import ghidra.program.model.data.CharsetInfo;
import ghidra.util.charset.CharsetInfoManager;
public class SoftwareModelingInitializer implements ModuleInitializer {
@Override
public void run() {
// Register user defined Charsets
CharsetInfo.reinitializeWithUserDefinedCharsets();
CharsetInfoManager.reinitializeWithUserDefinedCharsets();
}
@Override

View File

@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -19,6 +19,7 @@ import ghidra.docking.settings.*;
import ghidra.program.model.mem.MemBuffer;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.util.StringFormat;
import ghidra.util.charset.CharsetInfoManager;
import ghidra.util.classfinder.ClassTranslator;
/**
@@ -200,9 +201,9 @@ public class CharDataType extends AbstractIntegerDataType implements DataTypeWit
return CharsetSettingsDefinition.CHARSET.getCharset(settings,
StringDataInstance.DEFAULT_CHARSET_NAME);
case 2:
return CharsetInfo.UTF16;
return CharsetInfoManager.UTF16;
case 4:
return CharsetInfo.UTF32;
return CharsetInfoManager.UTF32;
default:
return StringDataInstance.DEFAULT_CHARSET_NAME;
}

View File

@@ -1,210 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.program.model.data;
import java.util.*;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import org.jdom.*;
import org.jdom.input.SAXBuilder;
import generic.jar.ResourceFile;
import ghidra.framework.Application;
import ghidra.util.Msg;
import ghidra.util.xml.XmlUtilities;
/**
* Additional information about {@link Charset java.nio.charset.Charset's} that
* Ghidra needs to be able to create Ghidra string datatype instances.
* <p>
* See charset_info.xml to specify a custom charset.
*/
public class CharsetInfo {
private static final class Singleton {
private static final CharsetInfo INSTANCE = new CharsetInfo();
}
/**
* Get the global singleton instance of this {@link CharsetInfo}.
*
* @return global singleton instance
*/
public static CharsetInfo getInstance() {
return Singleton.INSTANCE;
}
//-----------------------------------------------------------------------------
public static final String UTF8 = "UTF-8";
public static final String UTF16 = "UTF-16";
public static final String UTF32 = "UTF-32";
public static final String USASCII = "US-ASCII";
/**
* @param charsetName name of charset
* @return true if the supported multi-byte charset does not specify LE or
* BE
*/
public static boolean isBOMCharset(String charsetName) {
return UTF32.equals(charsetName) || UTF16.equals(charsetName);
}
//-----------------------------------------------------------------------------
private Map<String, CharsetInfoRec> charsetInfoRecsByName = new HashMap<>();
private List<String> charsetNamesList = new ArrayList<>();
private String[] charsetNames;
private CharsetInfo() {
initialize(false);
}
private void initialize(boolean includeConfigFile) {
registerStandardCharsets();
if (includeConfigFile) {
readConfigFile();
}
addJVMAvailableCharsets();
this.charsetNames = charsetNamesList.toArray(new String[charsetNamesList.size()]);
}
/**
* Reinitialize registered Charsets and include user defined Charsets
* specified in charset_info.xml.
*/
public static void reinitializeWithUserDefinedCharsets() {
getInstance().initialize(true);
}
/**
* Register minimal set of Java Charsets to facilitate most test without
* Application initialization overhead.
*/
private void registerStandardCharsets() {
if (charsetInfoRecsByName.isEmpty()) {
addCharset(USASCII, 1);
addCharset(UTF8, 1);
addCharset("ISO-8859-1", 1);
addCharset(UTF16, 2);
addCharset("UTF-16BE", 2);
addCharset("UTF-16LE", 2);
addCharset(UTF32, 4);
addCharset("UTF-32BE", 4);
addCharset("UTF-32LE", 4);
}
}
private void addCharset(String name, int charSize) {
CharsetInfoRec rec = new CharsetInfoRec(name, charSize);
charsetInfoRecsByName.put(name, rec);
charsetNamesList.add(name);
}
/**
* Returns an array list of the currently configured charsets.
*
* @return String[] of current configured charsets.
*/
public String[] getCharsetNames() {
return charsetNames;
}
/**
* Returns the number of bytes that the specified charset needs to specify a
* character.
*
* @param charsetName charset name
* @return number of bytes in a character, ie. 1, 2, 4, etc, defaults to 1
* if charset is unknown or not specified in config file.
*/
public int getCharsetCharSize(String charsetName) {
CharsetInfoRec rec = charsetInfoRecsByName.get(charsetName);
return (rec != null) ? rec.charSize : 1;
}
/**
* Returns list of {@link Charset}s that encode with the number of bytes specified.
* @param size the number of bytes for the {@link Charset} encoding.
* @return Charsets that encode one byte characters.
*/
public List<String> getCharsetNamesWithCharSize(int size) {
List<String> names = new ArrayList<>();
for (String name : charsetNames) {
if (getCharsetCharSize(name) == size) {
names.add(name);
}
}
return names;
}
private void addJVMAvailableCharsets() {
// Add charsets that can be discovered in the current JVM.
for (String csName : Charset.availableCharsets().keySet()) {
if (charsetInfoRecsByName.containsKey(csName)) {
continue;
}
addCharset(csName, 1);
}
}
@SuppressWarnings("unchecked")
private void readConfigFile() {
ResourceFile xmlFile = Application.findDataFileInAnyModule("charset_info.xml");
try (InputStream xmlInputStream = xmlFile.getInputStream()) {
SAXBuilder sax = XmlUtilities.createSecureSAXBuilder(false, false);
Document doc = sax.build(xmlInputStream);
Element root = doc.getRootElement();
for (Element child : (List<Element>) root.getChildren("charset")) {
try {
String name = child.getAttributeValue("name");
if (name == null || name.trim().isEmpty()) {
throw new IOException("Bad charset definition in " + xmlFile);
}
if (!Charset.isSupported(name)) {
Msg.warn(this,
"Unsupported charset defined in " + xmlFile.getName() + ": " + name);
}
int charSize = XmlUtilities.parseBoundedIntAttr(child, "charSize", 1, 8);
addCharset(name, charSize);
}
catch (NumberFormatException nfe) {
throw new IOException("Invalid charset definition in " + xmlFile);
}
}
}
catch (JDOMException | IOException e) {
Msg.showError(this, null, "Error reading charset data", e.getMessage(), e);
}
}
private static class CharsetInfoRec {
final String name;
final int charSize;
CharsetInfoRec(String name, int charSize) {
this.name = name;
this.charSize = charSize;
}
}
}

View File

@@ -18,6 +18,8 @@ package ghidra.program.model.data;
import java.util.*;
import ghidra.docking.settings.*;
import ghidra.util.charset.CharsetInfo;
import ghidra.util.charset.CharsetInfoManager;
/**
* {@link SettingsDefinition} for setting the charset of a string instance.
@@ -56,7 +58,7 @@ public class CharsetSettingsDefinition implements EnumSettingsDefinition {
private final Map<String, Integer> stringToOrdinal = new HashMap<>();
private CharsetSettingsDefinition() {
ordinalToString = CharsetInfo.getInstance().getCharsetNames();
ordinalToString = CharsetInfoManager.getInstance().getCharsetNames().toArray(String[]::new);
for (int i = 0; i < ordinalToString.length; i++) {
stringToOrdinal.put(ordinalToString[i], i);
}

View File

@@ -15,6 +15,8 @@
*/
package ghidra.program.model.data;
import ghidra.util.charset.CharsetInfoManager;
/**
* A length-prefixed string {@link DataType} (max 64k bytes) with char size of 2 bytes,
* {@link CharsetSettingsDefinition UTF-16} charset, unbounded
@@ -35,7 +37,7 @@ public class PascalUnicodeDataType extends AbstractStringDataType {
"P_UNI", // default label prefix
"pu", // default abbrev label prefix
"String (Pascal UTF-16 64k)", // description
CharsetInfo.UTF16, // charset
CharsetInfoManager.UTF16, // charset
ByteDataType.dataType, // replacement data type
StringLayoutEnum.PASCAL_64k, // StringLayoutEnum
dtm// data type manager

View File

@@ -34,6 +34,7 @@ import ghidra.program.model.lang.Endian;
import ghidra.program.model.listing.Data;
import ghidra.program.model.mem.*;
import ghidra.util.*;
import ghidra.util.charset.CharsetInfoManager;
/**
* Represents an instance of a string in a {@link MemBuffer}.
@@ -251,7 +252,7 @@ public class StringDataInstance {
public static final int MAX_STRING_LENGTH = 16 * 1024;
public static final String DEFAULT_CHARSET_NAME = CharsetInfo.USASCII;
public static final String DEFAULT_CHARSET_NAME = CharsetInfoManager.USASCII;
public static final String UNKNOWN = "??";
public static final String UNKNOWN_DOT_DOT_DOT = "??...";
@@ -322,7 +323,7 @@ public class StringDataInstance {
settings = (settings == null) ? SettingsImpl.NO_SETTINGS : settings;
this.buf = buf;
this.charsetName = getCharsetNameFromDataTypeOrSettings(dataType, settings);
this.charSize = CharsetInfo.getInstance().getCharsetCharSize(charsetName);
this.charSize = CharsetInfoManager.getInstance().getCharsetCharSize(charsetName);
// NOTE: for now only handle padding for charSize == 1 and the data type is an array of elements, not a "string"
this.paddedCharSize = (dataType instanceof ArrayStringable) && (charSize == 1) //
? getDataOrganization(dataType).getCharSize()
@@ -729,7 +730,7 @@ public class StringDataInstance {
private AdjustedCharsetInfo getAdjustedCharsetInfo(byte[] bytes) {
AdjustedCharsetInfo result = new AdjustedCharsetInfo(charsetName);
if (CharsetInfo.isBOMCharset(charsetName)) {
if (CharsetInfoManager.isBOMCharset(charsetName)) {
result.endian = getEndiannessFromBOM(bytes, charSize);
if (result.endian != null) {
// skip the BOM char when creating the string
@@ -753,7 +754,7 @@ public class StringDataInstance {
private String getAdjustedCharsetInfo(ByteBuffer bb) {
String result = charsetName;
if (CharsetInfo.isBOMCharset(charsetName)) {
if (CharsetInfoManager.isBOMCharset(charsetName)) {
Endian endian = getEndiannessFromBOM(bb, charSize);
if (endian == null) {
endian = endianSetting;
@@ -1030,15 +1031,18 @@ public class StringDataInstance {
dataTypeMap.put(new Pair<>(NULL_TERMINATED_UNBOUNDED, null),
TerminatedStringDataType.dataType);
dataTypeMap.put(new Pair<>(PASCAL_64k, CharsetInfo.UTF16), PascalUnicodeDataType.dataType);
dataTypeMap.put(new Pair<>(PASCAL_64k, CharsetInfoManager.UTF16),
PascalUnicodeDataType.dataType);
dataTypeMap.put(new Pair<>(FIXED_LEN, CharsetInfo.UTF8), StringUTF8DataType.dataType);
dataTypeMap.put(new Pair<>(FIXED_LEN, CharsetInfo.UTF16), UnicodeDataType.dataType);
dataTypeMap.put(new Pair<>(FIXED_LEN, CharsetInfo.UTF32), Unicode32DataType.dataType);
dataTypeMap.put(new Pair<>(FIXED_LEN, CharsetInfoManager.UTF8),
StringUTF8DataType.dataType);
dataTypeMap.put(new Pair<>(FIXED_LEN, CharsetInfoManager.UTF16), UnicodeDataType.dataType);
dataTypeMap.put(new Pair<>(FIXED_LEN, CharsetInfoManager.UTF32),
Unicode32DataType.dataType);
dataTypeMap.put(new Pair<>(NULL_TERMINATED_UNBOUNDED, CharsetInfo.UTF16),
dataTypeMap.put(new Pair<>(NULL_TERMINATED_UNBOUNDED, CharsetInfoManager.UTF16),
TerminatedUnicodeDataType.dataType);
dataTypeMap.put(new Pair<>(NULL_TERMINATED_UNBOUNDED, CharsetInfo.UTF32),
dataTypeMap.put(new Pair<>(NULL_TERMINATED_UNBOUNDED, CharsetInfoManager.UTF32),
TerminatedUnicode32DataType.dataType);
}

View File

@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -22,6 +22,7 @@ import java.util.stream.Collectors;
import ghidra.program.model.lang.Endian;
import ghidra.util.StringUtilities;
import ghidra.util.charset.CharsetInfoManager;
import ghidra.util.exception.UsrException;
/**
@@ -60,18 +61,18 @@ public class StringRenderParser {
private final boolean isFinal;
private final Set<Character> accepts;
private State(boolean isFinal) { // Implies this accepts any
State(boolean isFinal) { // Implies this accepts any
this.isFinal = isFinal;
this.accepts = null;
}
private State(boolean isFinal, String accepts) {
State(boolean isFinal, String accepts) {
this(isFinal, accepts.chars()
.mapToObj(i -> (char) i)
.collect(Collectors.toSet()));
}
private State(boolean isFinal, Set<Character> accepts) {
State(boolean isFinal, Set<Character> accepts) {
this.isFinal = isFinal;
this.accepts = Collections.unmodifiableSet(accepts);
}
@@ -140,8 +141,8 @@ public class StringRenderParser {
protected void initCharset(ByteBuffer out, String reprCharsetName) {
String charsetName = this.charsetName != null ? this.charsetName : reprCharsetName;
int charSize = CharsetInfo.getInstance().getCharsetCharSize(charsetName);
if (CharsetInfo.isBOMCharset(charsetName)) {
int charSize = CharsetInfoManager.getInstance().getCharsetCharSize(charsetName);
if (CharsetInfoManager.isBOMCharset(charsetName)) {
// Take care of the BOM ourselves, because it must be first, before any initial bytes
charsetName += endian.isBigEndian() ? "BE" : "LE";
}
@@ -175,19 +176,19 @@ public class StringRenderParser {
return State.PREFIX;
}
if (c == 'U') {
initCharset(out, CharsetInfo.UTF32);
initCharset(out, CharsetInfoManager.UTF32);
return State.UNIT;
}
initCharset(out, CharsetInfo.USASCII);
initCharset(out, CharsetInfoManager.USASCII);
return parseCharUnit(out, c);
}
protected State parseCharPrefix(ByteBuffer out, char c) {
if (c == '8') {
initCharset(out, CharsetInfo.UTF8);
initCharset(out, CharsetInfoManager.UTF8);
return State.UNIT;
}
initCharset(out, CharsetInfo.UTF16);
initCharset(out, CharsetInfoManager.UTF16);
return parseCharUnit(out, c);
}

View File

@@ -15,6 +15,8 @@
*/
package ghidra.program.model.data;
import ghidra.util.charset.CharsetInfoManager;
/**
* A fixed-length UTF-8 string {@link DataType}.
*/
@@ -32,7 +34,7 @@ public class StringUTF8DataType extends AbstractStringDataType {
"STR", // default label prefix
"s", // default abbrev label prefix
"String (Fixed Length UTF-8 Unicode)", // description
CharsetInfo.UTF8, // charset
CharsetInfoManager.UTF8, // charset
CharDataType.dataType, // replacement data type
StringLayoutEnum.FIXED_LEN, // StringLayoutEnum
dtm// data type manager

View File

@@ -15,6 +15,8 @@
*/
package ghidra.program.model.data;
import ghidra.util.charset.CharsetInfoManager;
/**
* A null-terminated UTF-32 string {@link DataType}.
*/
@@ -33,7 +35,7 @@ public class TerminatedUnicode32DataType extends AbstractStringDataType {
"UNI", // default label prefix
"u", // default abbrev label prefix
"String (Null Terminated UTF-32 Unicode)", // description
CharsetInfo.UTF32, // charset
CharsetInfoManager.UTF32, // charset
WideChar32DataType.dataType, // replacement data type
StringLayoutEnum.NULL_TERMINATED_UNBOUNDED, // StringLayoutEnum
dtm// data type manager

View File

@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -15,6 +15,8 @@
*/
package ghidra.program.model.data;
import ghidra.util.charset.CharsetInfoManager;
/**
* A null-terminated string {@link DataType} with a UTF-16 {@link CharsetSettingsDefinition charset}.
* <p>
@@ -38,7 +40,7 @@ public class TerminatedUnicodeDataType extends AbstractStringDataType {
DEFAULT_UNICODE_LABEL_PREFIX, // default label prefix
DEFAULT_UNICODE_ABBREV_PREFIX, // default abbrev label prefix
"String (Null Terminated UTF-16 Unicode)", // description
CharsetInfo.UTF16, // charset
CharsetInfoManager.UTF16, // charset
WideChar16DataType.dataType, // replacement data type
StringLayoutEnum.NULL_TERMINATED_UNBOUNDED, // StringLayoutEnum
dtm// data type manager

View File

@@ -15,6 +15,8 @@
*/
package ghidra.program.model.data;
import ghidra.util.charset.CharsetInfoManager;
/**
* A fixed-length UTF-32 string {@link DataType}.
*/
@@ -36,7 +38,7 @@ public class Unicode32DataType extends AbstractStringDataType {
"UNI", // default label prefix
"u", // default abbrev label prefix
"String (Fixed Length UTF-32 Unicode)", // description
CharsetInfo.UTF32, // charset
CharsetInfoManager.UTF32, // charset
WideChar32DataType.dataType, // replacement data type
StringLayoutEnum.FIXED_LEN, // StringLayoutEnum
dtm// data type manager

View File

@@ -15,6 +15,8 @@
*/
package ghidra.program.model.data;
import ghidra.util.charset.CharsetInfoManager;
/**
* A fixed-length UTF-16 string {@link DataType}.
*/
@@ -33,7 +35,7 @@ public class UnicodeDataType extends AbstractStringDataType {
"UNI", // default label prefix
"u", // default abbrev label prefix
"String (Fixed Length UTF-16 Unicode)", // description
CharsetInfo.UTF16, // charset
CharsetInfoManager.UTF16, // charset
WideChar16DataType.dataType, // replacement data type
StringLayoutEnum.FIXED_LEN, // StringLayoutEnum
dtm// data type manager

View File

@@ -20,6 +20,7 @@ import ghidra.docking.settings.SettingsDefinition;
import ghidra.program.model.mem.MemBuffer;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.util.StringUtilities;
import ghidra.util.charset.CharsetInfoManager;
public class WideChar16DataType extends BuiltIn implements ArrayStringable, DataTypeWithCharset {
@@ -152,6 +153,6 @@ public class WideChar16DataType extends BuiltIn implements ArrayStringable, Data
@Override
public String getCharsetName(Settings settings) {
return CharsetInfo.UTF16;
return CharsetInfoManager.UTF16;
}
}

View File

@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -21,6 +21,7 @@ import ghidra.program.model.mem.MemBuffer;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.program.model.scalar.Scalar;
import ghidra.util.StringUtilities;
import ghidra.util.charset.CharsetInfoManager;
public class WideChar32DataType extends BuiltIn implements ArrayStringable, DataTypeWithCharset {
@@ -154,7 +155,7 @@ public class WideChar32DataType extends BuiltIn implements ArrayStringable, Data
@Override
public String getCharsetName(Settings settings) {
return CharsetInfo.UTF32;
return CharsetInfoManager.UTF32;
}
}

View File

@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -21,6 +21,7 @@ import ghidra.program.model.mem.MemBuffer;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.program.model.scalar.Scalar;
import ghidra.util.StringUtilities;
import ghidra.util.charset.CharsetInfoManager;
public class WideCharDataType extends BuiltIn implements ArrayStringable, DataTypeWithCharset {
final static SettingsDefinition[] DEFAULT_WIDE_CHAR_SETTINGS = new SettingsDefinition[] {
@@ -186,9 +187,9 @@ public class WideCharDataType extends BuiltIn implements ArrayStringable, DataTy
public String getCharsetName(Settings settings) {
switch (getLength()) {
case 2:
return CharsetInfo.UTF16;
return CharsetInfoManager.UTF16;
case 4:
return CharsetInfo.UTF32;
return CharsetInfoManager.UTF32;
default:
return StringDataInstance.DEFAULT_CHARSET_NAME;
}

View File

@@ -0,0 +1,192 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.util.charset;
import java.lang.Character.UnicodeScript;
import java.nio.charset.Charset;
import java.util.*;
import generic.json.Json;
/**
* Additional information about {@link Charset java.nio.charset.Charset's} that
* Ghidra needs to be able to create Ghidra string datatype instances.
* <p>
* See charset_info.json to specify info about a custom charset.
*/
public class CharsetInfo {
static final Set<String> FIELDS_TO_EXCLUDE_FROM_JSON = Set.of("standardCharset");
static final int UNICODESCRIPT_COUNT = UnicodeScript.UNKNOWN.ordinal() + 1;
static final EnumSet<UnicodeScript> ALL_SCRIPTS = EnumSet.allOf(UnicodeScript.class);
static final EnumSet<UnicodeScript> NO_SCRIPTS = EnumSet.noneOf(UnicodeScript.class);
private final String name;
private final String comment;
private final int minBytesPerChar;
private final int maxBytesPerChar;
private final int alignment;
private final int codePointCount;
private final EnumSet<UnicodeScript> scripts;
private final Set<String> contains;
private final boolean canProduceError;
private final boolean standardCharset; // not serialized, see FIELDS_TO_EXCLUDE_FROM_JSON
public CharsetInfo(Charset cs) {
this(cs.name(), null, 1, -1, 1, -1, false, true, NO_SCRIPTS, Set.of());
}
public CharsetInfo(String name, String comment, int minBytesPerChar, int maxBytesPerChar,
int alignment, int codePointCount, boolean standardCharset, boolean canProduceError,
EnumSet<UnicodeScript> scripts, Set<String> contains) {
this.name = name;
this.comment = comment;
this.minBytesPerChar = minBytesPerChar;
this.maxBytesPerChar = maxBytesPerChar;
this.alignment = alignment;
this.codePointCount = codePointCount;
this.standardCharset = standardCharset;
this.canProduceError = canProduceError;
this.scripts = scripts;
this.contains = contains;
}
/**
* {@return a copy of this instance, with a new comment value}
* @param newComment string
*/
public CharsetInfo withComment(String newComment) {
return new CharsetInfo(name, newComment, minBytesPerChar, maxBytesPerChar, alignment,
codePointCount, standardCharset, canProduceError, scripts, contains);
}
/**
* @return {@link Charset}
*/
public Charset getCharset() {
return Charset.forName(name, null);
}
/**
* {@return name of the charset}
*/
public String getName() {
return name;
}
/**
* {@return boolean flag, true if this is a standard charset that is guaranteed to be present
* in the jvm, otherwise false}
*/
public boolean isStandardCharset() {
return standardCharset;
}
/**
* {@return true if this charset can produce Unicode REPLACEMENT codepoints for
* bad byte sequences, otherwise false if there are no byte sequences that result in REPLACEMENT
* codepoints. This is typically single-byte charsets that map all byte values to a codepoint}
*/
public boolean isCanProduceError() {
return canProduceError;
}
/**
* {@return true if this charset can produce Unicode codepoints that are in all scripts}
*/
public boolean supportsAllScripts() {
return scripts.size() >= UNICODESCRIPT_COUNT - 1 /* ignore unknown */;
}
/**
* {@return the UnicodeScripts that this charset can produce}
*/
public Set<UnicodeScript> getScripts() {
return scripts;
}
/**
* {@return true if this charset only consumes a fixed number of bytes per output codepoint}
*/
public boolean hasFixedLengthChars() {
return minBytesPerChar > 0 && minBytesPerChar == maxBytesPerChar;
}
/**
* {@return the alignment value for this charset, typically 1 for most charsets, but for
* well-known fixed-width charsets, it will return those charsets fixed-width}
*/
public int getAlignment() {
return alignment;
}
/**
* {@return the smallest number of bytes needed to produce a codepoint}
*/
public int getMinBytesPerChar() {
return minBytesPerChar;
}
/**
* {@return the largest number of bytes needed to produce a codepoint}
*/
public int getMaxBytesPerChar() {
return maxBytesPerChar;
}
/**
* {@return the number of codepoints that this charset can produce}
*/
public int getCodePointCount() {
return codePointCount;
}
/**
* Returns the names of other charsets that this charset {@link Charset#contains(Charset)}.
*
* @return names of other charsets
*/
public Set<String> getContains() {
return contains;
}
/**
* {@return a string comment describing this charset, or null}
*/
public String getComment() {
return comment;
}
@Override
public int hashCode() {
return Objects.hash(name);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof CharsetInfo other)) {
return false;
}
return Objects.equals(name, other.name);
}
@Override
public String toString() {
return Json.toString(this);
}
}

View File

@@ -0,0 +1,375 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.util.charset;
import static java.lang.Character.UnicodeScript.*;
import static java.nio.charset.StandardCharsets.*;
import java.io.*;
import java.lang.Character.UnicodeScript;
import java.nio.charset.Charset;
import java.util.*;
import com.google.gson.*;
import com.google.gson.stream.JsonReader;
import generic.jar.ResourceFile;
import ghidra.framework.Application;
import ghidra.util.Msg;
/**
* Maintains a list of charsets and info about each charset. More common charsets are ordered
* toward the beginning of the list.
* <p>
* Created instances are immutable, but the "INSTANCE" singleton can be replaced by a new value
* when {@link #reinitializeWithUserDefinedCharsets()} is called. (This is done to avoid reading
* the user config file and causing slow downs during certain stages of the startup)
*/
public class CharsetInfoManager {
public static final String UTF8 = "UTF-8";
public static final String UTF16 = "UTF-16";
public static final String UTF32 = "UTF-32";
public static final String USASCII = "US-ASCII";
/**
* Comparator that ignores charset name "x-" prefixes
*/
public static Comparator<String> CHARSET_NAME_COMP = (s1, s2) -> {
return stripCharsetX(s1).compareToIgnoreCase(stripCharsetX(s2));
};
/**
* Comparator that ignores charset name "x-" prefixes
*/
public static Comparator<CharsetInfo> CHARSET_COMP = (csi1, csi2) -> {
return stripCharsetX(csi1.getName()).compareToIgnoreCase(stripCharsetX(csi2.getName()));
};
private static final class Singleton {
private static CharsetInfoManager INSTANCE = new CharsetInfoManager();
}
private static final class CharSetsSingleton {
// decouple loading these 'non-standard' (but actually standard) charsets to a different
// class so their initialization can happen before the manager singleton instance
private static final Charset UTF_32 = Charset.forName(UTF32);
private static final Charset UTF_32LE = Charset.forName("UTF-32LE");
private static final Charset UTF_32BE = Charset.forName("UTF-32BE");
}
/**
* Get the global singleton instance of this {@link CharsetInfoManager}.
* <p>
* This singleton will only have generic information until
* {@link CharsetInfoManager#reinitializeWithUserDefinedCharsets()} is called.
*
* @return global singleton instance
*/
public static CharsetInfoManager getInstance() {
return Singleton.INSTANCE;
}
/**
* {@return true if the specified charset needs additional care for handling byte-order-mark
* byte values (eg. UTF-16/32). If the charset is a LE/BE variant, no extra care is needed.}
* @param charsetName name of charset
*/
public static boolean isBOMCharset(String charsetName) {
return UTF32.equals(charsetName) || UTF16.equals(charsetName);
}
private static String stripCharsetX(String csName) {
return csName.startsWith("x-") ? csName.substring(2) : csName;
}
private Map<String, CharsetInfo> charsets = new LinkedHashMap<>(); // preserve addition order
private CharsetInfoManager() {
this(List.of());
}
private CharsetInfoManager(List<CharsetInfo> userDefinedInfo) {
// add ASCII+UTF-NN charsets first (these are the most commonly used)
getStandardCharsets().forEach(csi -> charsets.put(csi.getName(), csi));
// add user defined charsets (that are present in the jvm)
userDefinedInfo.forEach(csi -> {
if (Charset.isSupported(csi.getName())) {
charsets.put(csi.getName(), csi);
}
});
// last, add any charsets that are not covered by the standard and user-defined charsets
List<String> availCSNames = new ArrayList<>(Charset.availableCharsets().keySet());
availCSNames.sort(CHARSET_NAME_COMP);
for (String csName : availCSNames) {
if (!charsets.containsKey(csName)) {
charsets.put(csName, new CharsetInfo(Charset.forName(csName)));
}
}
}
/**
* {@return List of names of current configured charsets}
*/
public List<String> getCharsetNames() {
return List.copyOf(charsets.keySet());
}
/**
* {@return list of all available charsets}
*/
public List<CharsetInfo> getCharsets() {
return List.copyOf(charsets.values());
}
/**
* Returns the number of bytes that the specified charset needs to specify a
* character.
*
* @param charsetName charset name
* @return number of bytes in a character, ie. 1, 2, 4, etc, defaults to 1
* if charset is unknown or not specified in config file.
*/
public int getCharsetCharSize(String charsetName) {
CharsetInfo csi = charsets.get(charsetName);
return (csi != null) ? csi.getMinBytesPerChar() : 1;
}
/**
* Returns list of {@link Charset}s that encode with the number of bytes specified.
* @param size the number of bytes for the {@link Charset} encoding.
* @return Charsets that encode one byte characters.
*/
public List<String> getCharsetNamesWithCharSize(int size) {
return charsets.values()
.stream()
.filter(csi -> csi.getMinBytesPerChar() == size)
.map(csi -> csi.getName())
.toList();
}
/**
* {@return charset info object that represents the specified charset}
* @param cs charset
*/
public CharsetInfo get(Charset cs) {
return charsets.get(cs.name());
}
/**
* {@return charset info object that represents the specified charset}
* @param name charset name
*/
public CharsetInfo get(String name) {
return charsets.get(name);
}
/**
* {@return charset info object that represents the specified charset, and if not found,
* returning the defaultCS value}
*
* @param name charset name
* @param defaultCS default value to return if not found
*/
public CharsetInfo get(String name, Charset defaultCS) {
CharsetInfo result = charsets.get(name);
if (result == null && defaultCS != null) {
result = charsets.get(defaultCS.name());
}
return result;
}
/**
* {@return a hopefully short list of non-LATIN UnicodeScripts that are supported by a
* charset that is present in this jvm. (ignoring any charsets that support all scripts).
* This list of scripts can be useful when presenting the user with a list of scripts or
* things related to a script. Typically the list will contain:
* ARABIC, BOPOMOFO, CYRILLIC, DEVANAGARI, HANGUL, HAN, HEBREW, HIRAGANA, KATAKANA, THAI }
*/
public List<UnicodeScript> getMostImplementedScripts() {
Set<UnicodeScript> scriptsToIgnore = EnumSet.of(COMMON, INHERITED, UNKNOWN, LATIN, GREEK);
List<UnicodeScript> scripts = charsets.values()
.stream()
.filter(csi -> !csi.supportsAllScripts())
.flatMap(csi -> csi.getScripts().stream())
.filter(script -> !scriptsToIgnore.contains(script))
.sorted((o1, o2) -> o1.name().compareTo(o2.name()))
.distinct()
.toList();
return scripts;
}
//---------------------------------------------------------------------------------------------
// static helper methods
//---------------------------------------------------------------------------------------------
public static List<String> getStandardCharsetNames() {
return List.of(USASCII, UTF8, UTF16, UTF32);
}
private static List<CharsetInfo> getStandardCharsets() {
//@formatter:off
return List.of(
new CharsetInfo(USASCII, null, 1, 1, 1, -1, true, true, EnumSet.of(COMMON, LATIN), Set.of()),
new CharsetInfo(UTF8, null, 1, 4, 1, -1, true, true, CharsetInfo.ALL_SCRIPTS, Set.of()),
new CharsetInfo(UTF16, null, 2, 4, 2, -1, true, true, CharsetInfo.ALL_SCRIPTS, Set.of()),
new CharsetInfo(UTF_16BE.name(), null, 2, 4, 2, -1, true, true, CharsetInfo.ALL_SCRIPTS, Set.of()),
new CharsetInfo(UTF_16LE.name(), null, 2, 4, 2, -1, true, true, CharsetInfo.ALL_SCRIPTS, Set.of()),
new CharsetInfo(CharSetsSingleton.UTF_32.name(), null, 4, 4, 4, -1, true, true, CharsetInfo.ALL_SCRIPTS, Set.of()),
new CharsetInfo(CharSetsSingleton.UTF_32BE.name(), null, 4, 4, 4, -1, true, true, CharsetInfo.ALL_SCRIPTS, Set.of()),
new CharsetInfo(CharSetsSingleton.UTF_32LE.name(), null, 4, 4, 4, -1, true, true, CharsetInfo.ALL_SCRIPTS, Set.of()),
new CharsetInfo(ISO_8859_1.name(), null, 1, 1, 1, -1, true, false, EnumSet.of(COMMON, LATIN), Set.of(USASCII))
);
//@formatter:on
}
/**
* Replaces the current singleton with a new singleton that has been initialized with the
* optional information found in the charset_info.json file.
*/
public static void reinitializeWithUserDefinedCharsets() {
CharsetInfoConfigFile configFile = CharsetInfoConfigFile.read(getConfigFileLocation());
if (!configFile.getCharsets().isEmpty()) {
Singleton.INSTANCE = new CharsetInfoManager(configFile.getCharsets());
}
}
/**
* {@return filename of the config file}
*/
public static ResourceFile getConfigFileLocation() {
return Application.findDataFileInAnyModule("charset_info.json");
}
/**
* Class to represent the charsetinfo json configuration file.
*/
public static class CharsetInfoConfigFile {
/**
* Read config info from the specified file
*
* @param configFile {@link ResourceFile}
* @return new {@link CharsetInfoConfigFile}, never null, but maybe empty
*/
public static CharsetInfoConfigFile read(ResourceFile configFile) {
if (configFile != null) {
try (InputStream is = configFile.getInputStream();
JsonReader reader = new JsonReader(new InputStreamReader(is))) {
Gson gson = new GsonBuilder().create();
CharsetInfoConfigFile configFileData =
gson.fromJson(reader, CharsetInfoConfigFile.class);
if (configFileData == null) {
return new CharsetInfoConfigFile();
}
configFileData.validateData();
return configFileData;
}
catch (JsonParseException | IOException e) {
Msg.error(CharsetInfoManager.class, "Error reading charset_info.json", e);
// fall thru, return default empty instance
}
}
return new CharsetInfoConfigFile();
}
private List<String> comments; // broken up into a list so it looks good in the json
private List<CharsetInfo> charsets;
public CharsetInfoConfigFile() {
this.comments = List.of();
this.charsets = List.of();
}
public CharsetInfoConfigFile(String comment, List<CharsetInfo> charsets) {
this.comments = comment.lines().toList();
this.charsets = charsets;
}
public String getComment() {
return String.join("\n", comments);
}
public List<CharsetInfo> getCharsets() {
return charsets;
}
public void validateData() {
Set<String> names = new HashSet<>();
Set<String> dups = new HashSet<>();
Set<String> unknowns = new HashSet<>();
for (CharsetInfo csi : charsets) {
if (!names.add(csi.getName())) {
dups.add(csi.getName());
}
if (!Charset.isSupported(csi.getName())) {
unknowns.add(csi.getName());
}
}
if (!dups.isEmpty()) {
Msg.warn(CharsetInfoManager.class,
"Duplicate charset names found in charset_info.json: " + dups);
}
if (!unknowns.isEmpty()) {
Msg.warn(CharsetInfoManager.class,
"Unknown/unsupported charset names found in charset_info.json: " + unknowns);
}
}
/**
* Writes this instance to a json file.
*
* @param configFilename where to write to
* @throws IOException if error writing
*/
public void write(File configFilename) throws IOException {
File configDir = configFilename.getParentFile();
File tmpConfigFile = new File(configDir, configFilename.getName() + ".tmp");
File prevConfigFile = new File(configDir, configFilename.getName() + ".prev");
try (Writer fw = new FileWriter(tmpConfigFile)) {
Gson gson = new GsonBuilder().setPrettyPrinting()
.addSerializationExclusionStrategy(new ExclusionStrategy() {
@Override
public boolean shouldSkipField(FieldAttributes f) {
return f.getDeclaringClass().equals(CharsetInfo.class) &&
CharsetInfo.FIELDS_TO_EXCLUDE_FROM_JSON.contains(f.getName());
}
@Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
})
.create();
gson.toJson(this, fw);
}
prevConfigFile.delete();
configFilename.renameTo(prevConfigFile);
if (tmpConfigFile.renameTo(configFilename)) {
prevConfigFile.delete();
}
}
}
}

View File

@@ -0,0 +1,170 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.util.charset;
import static java.lang.Character.UnicodeScript.*;
import static org.junit.Assert.*;
import java.io.File;
import java.io.IOException;
import java.lang.Character.UnicodeScript;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.*;
import java.util.*;
import org.junit.Ignore;
import org.junit.Test;
import generic.jar.ResourceFile;
import generic.test.AbstractGenericTest;
import ghidra.util.Msg;
import ghidra.util.StringUtilities;
import ghidra.util.charset.CharsetInfoManager.CharsetInfoConfigFile;
public class CharsetInfoManagerTest extends AbstractGenericTest {
@Test
public void testCharsetsArePresent() {
int jvmCharsetCount = Charset.availableCharsets().size();
int csimCount = CharsetInfoManager.getInstance().getCharsetNames().size();
assertEquals(jvmCharsetCount, csimCount);
}
/**
* Generates information about the charsets that are available in the current JVM. This can
* take a couple of minutes, and only needs to be done when there are new charsets of interest.
* <p>
* Attempts to preserve user-defined comments present in the previous .json file.
* <p>
* To execute, comment out the Ignore annotation and run as a junit. Do not leave enabled.
*
* @throws IOException if error
*/
@Ignore("temporarily enable and run when new charsets need to be categorized")
@Test
public void generateCharsetInfoFile() throws IOException {
File configFilename = CharsetInfoManager.getConfigFileLocation().getFile(false);
CharsetInfoConfigFile configFile =
CharsetInfoManager.CharsetInfoConfigFile.read(new ResourceFile(configFilename));
List<CharsetInfo> existingInfo = new ArrayList<>(configFile.getCharsets());
Msg.info(this, "Read " + existingInfo.size() + " previous records");
List<CharsetInfo> newInfo = new ArrayList<>();
Set<String> standardCharsetNames = Set.copyOf(CharsetInfoManager.getStandardCharsetNames());
for (String csName : Charset.availableCharsets().keySet()) {
if (standardCharsetNames.contains(csName) || csName.toLowerCase().contains("utf-")) {
continue;
}
Charset cs = Charset.forName(csName);
CharsetInfo csi = getCharsetInfoViaEncoder(cs);
if (csi != null) {
updateOrAppend(existingInfo, newInfo, csi);
}
Msg.info(this, "%s: %s".formatted(csName, csi != null ? "SUCCESS" : "NO INFO"));
}
newInfo.sort(CharsetInfoManager.CHARSET_COMP);
existingInfo.addAll(newInfo);
String comment = """
Information about character encodings used by Ghidra.
Generated on %s by CharsetInfoManagerTest.generateCharsetInfoFile
""".formatted(new Date());
CharsetInfoConfigFile newConfigFile = new CharsetInfoConfigFile(comment, existingInfo);
newConfigFile.write(configFilename);
Msg.info(this, "Done");
}
private void updateOrAppend(List<CharsetInfo> existingList, List<CharsetInfo> newList,
CharsetInfo newInfo) {
for (int i = 0; i < existingList.size(); i++) {
CharsetInfo existing = existingList.get(i);
if (existing.getName().equals(newInfo.getName())) {
if (newInfo.getComment() == null && existing.getComment() != null) {
newInfo = newInfo.withComment(existing.getComment());
}
existingList.set(i, newInfo);
return;
}
}
newList.add(newInfo);
}
private CharsetInfo getCharsetInfoViaEncoder(Charset cs) {
// Creates a CharsetInfo by using the charset's encoder to test what happens for each
// of the 1.1M unicode codepoints.
// NOTE: trying to use a charset's decoder to decode every byte sequence into codepoints is
// not computationally feasible for byte sequences longer than 3.
EnumSet<UnicodeScript> scripts = EnumSet.noneOf(UnicodeScript.class);
Set<Integer> byteLens = new HashSet<>();
int goodCPCount = 0;
CharsetEncoder encoder = null;
try {
encoder = cs.newEncoder();
}
catch (UnsupportedOperationException e) {
return null;
}
for (int cp = 1; cp <= Character.MAX_CODE_POINT; cp++) {
if (cp == StringUtilities.UNICODE_REPLACEMENT || UnicodeScript.of(cp) == UNKNOWN) {
continue;
}
String s = Character.toString(cp);
if (!encoder.canEncode(s)) {
continue;
}
try {
CharBuffer cb = CharBuffer.wrap(s);
ByteBuffer bb = encoder.encode(cb);
goodCPCount++;
scripts.add(UnicodeScript.of(cp));
byte[] bytes = new byte[bb.limit()];
bb.get(bytes);
byteLens.add(bytes.length);
}
catch (CharacterCodingException e) {
// skip
}
}
IntSummaryStatistics stats =
byteLens.stream().mapToInt(Integer::intValue).summaryStatistics();
// all 255 byte values produce a valid unicode codepoint, with no error mappings possible
boolean singleByteFullyMappedCS =
stats.getMin() == 1 && stats.getMax() == 1 && goodCPCount == 255;
CharsetInfo csi = new CharsetInfo(cs.name(), null, stats.getMin(), stats.getMax(), 1,
goodCPCount, false, !singleByteFullyMappedCS, scripts, getCSContains(cs));
return csi;
}
private Set<String> getCSContains(Charset cs) {
Set<String> result = new HashSet<>();
for (String csName : Charset.availableCharsets().keySet()) {
if (!csName.equals(cs.name()) && cs.contains(Charset.forName(csName))) {
result.add(csName);
}
}
return result;
}
}

View File

@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -18,9 +18,10 @@ package help.screenshot;
import org.junit.Test;
import docking.ComponentProvider;
import docking.DialogComponentProvider;
import ghidra.app.plugin.core.byteviewer.ByteViewerComponentProvider;
import ghidra.app.plugin.core.clear.ClearCmd;
import ghidra.app.util.AddressInput;
import ghidra.app.plugin.core.codebrowser.CodeViewerProvider;
import ghidra.app.plugin.core.datamgr.DataTypesProvider;
import ghidra.program.model.address.AddressSet;
public class ByteViewerPluginScreenShots extends GhidraScreenShotGenerator {
@@ -29,55 +30,62 @@ public class ByteViewerPluginScreenShots extends GhidraScreenShotGenerator {
super();
}
@Test
public void testByteViewer() {
@Test
public void testByteViewer() {
closeProvider(DataTypesProvider.class);
closeProvider(CodeViewerProvider.class);
setToolSize(500, 400);
ComponentProvider provider = getProvider("Bytes");
showProvider(provider.getClass());
goToListing(0x400000);
captureIsolatedProvider(provider.getClass(), 500, 400);
}
@Test
public void testByteViewerOptionsDialog() {
@Test
public void testByteViewerOptionsDialog() {
performAction("Byte Viewer Options", "ByteViewerPlugin", false);
captureDialog();
}
@Test
public void testByteViewerExample() {
@Test
public void testByteViewerExample() {
AddressSet set = new AddressSet(addr(0x40b000));
ClearCmd cmd = new ClearCmd(set);
tool.execute(cmd, program);
closeProvider(DataTypesProvider.class);
closeProvider(CodeViewerProvider.class);
setToolSize(500, 400);
ComponentProvider provider = getProvider("Bytes");
showProvider(provider.getClass());
goToListing(0x41cc08);
goToListing(0x40b003);
captureIsolatedProvider(provider.getClass(), 500, 400);
}
@Test
public void testByteViewerResults() {
@Test
public void testByteViewerResults() {
AddressSet set = new AddressSet(addr(0x40b000));
ClearCmd cmd = new ClearCmd(set);
tool.execute(cmd, program);
ComponentProvider provider = getProvider("Bytes");
closeProvider(DataTypesProvider.class);
closeProvider(CodeViewerProvider.class);
setToolSize(500, 400);
ByteViewerComponentProvider provider = getProvider(ByteViewerComponentProvider.class);
showProvider(provider.getClass());
performAction("Byte Viewer Options", "ByteViewerPlugin", false);
goToListing(0x41cc08);
goToListing(0x40b003);
final DialogComponentProvider dialog = getDialog();
runSwing(new Runnable() {
@Override
public void run() {
AddressInput addressInput =
(AddressInput) getInstanceField("addressInputField", dialog);
addressInput.setAddress(addr(0x40b003));
}
});
runSwing(() -> provider.setOffset(13));
pressOkOnDialog();
goToListing(0x41cc08);
goToListing(0x40b000);
goToListing(0x40b003);

View File

@@ -0,0 +1,37 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package help.screenshot;
import org.junit.Test;
import docking.DockingDialog;
import ghidra.util.charset.CharsetInfoManager;
import ghidra.util.charset.picker.CharsetPickerDialog;
public class CharsetsScreenShots extends GhidraScreenShotGenerator {
@Test
public void testCharsetPickerDialog() {
CharsetPickerDialog dlg = new CharsetPickerDialog();
showDialogWithoutBlocking(tool, dlg);
DockingDialog dialog = (DockingDialog) getInstanceField("dialog", dlg);
runSwing(() -> dialog.setSize(700, 500));
runSwing(() -> dlg.setSelectedCharset(CharsetInfoManager.getInstance().get("IBM437")));
captureDialog(CharsetPickerDialog.class);
}
}