Merge remote-tracking branch
'origin/GP-5689_dev747368_byteviewer_charset_column--SQUASHED'
@@ -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;
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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|
|
||||
|
||||
@@ -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]
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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 'Δ' 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>
|
||||
|
After Width: | Height: | Size: 38 KiB |
@@ -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();
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 -> "???";
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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) {}
|
||||
@@ -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. 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. </p>
|
||||
tool</a>, the Code Browser.</p>
|
||||
|
||||
<p align="center"><img alt="" src="images/ByteViewer.png" ><br> </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. 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. To add or remove a data format view
|
||||
from the tool, press the <img alt="" src="images/wrench.png">
|
||||
icon to bring up the <span
|
||||
style="font-style: italic;">Byte Viewer Options </span>dialog.
|
||||
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. 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. </p>
|
||||
displayed by default.</p>
|
||||
|
||||
<p> This view supports byte <a href="#EditBytes">editing</a>. </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>. </p>
|
||||
</blockquote>
|
||||
|
||||
<h3><a name="Add_Byteviewer_Address_Panel"></a><a name="Address"></a>Address</h3>
|
||||
<blockquote>
|
||||
<p> The Address view displays 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">
|
||||
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. </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. </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. </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. </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 </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>
|
||||
<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 labels below the scroll pane that contains the views shows the following information:</p>
|
||||
<p> </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. </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> </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, </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 Ascii </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>
|
||||
<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>. </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.
|
||||
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 </h3>
|
||||
<blockquote>
|
||||
<p>The alignment address specifies what address should appear in
|
||||
column 0. 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, since the HexInteger and Integer 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. Select the
|
||||
checkboxes corresponding to the views to be shown. 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. </li>
|
||||
<li>Edit your <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>
|
||||
|
||||
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 24 KiB |
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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.
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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|
|
||||
|
||||
3213
Ghidra/Framework/SoftwareModeling/data/charset_info.json
Normal 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>
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||