GP-5689 Add ByteViewer 'Chars' column
Convert the Ascii format to Chars, which supports decoding bytes using any available charset. Reworked the memory alignment option of the ByteViewer to allow the user to bump the displayed bytes left/right using an action. Add option to control how widely spaced the chars are displayed, to allow for wider non-Latin characters to be displayed correctly. Refactor the CharsetInfo class to better support the information needed by the new chars column. Fixed HexLongLong format to not mix BE / LE longs when constructing its final 16 byte value. Fixed handling of ByteViewer's background color to use its theme 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.
|
||||
@@ -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) {}
|
||||
|
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);
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -20,8 +19,25 @@ import ghidra.app.context.NavigatableActionContext;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||