mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-20 01:02:17 +08:00
Merge remote-tracking branch 'origin/GP-6453_ghidragon_structure_merge_gui'
This commit is contained in:
@@ -351,6 +351,7 @@ src/main/help/help/topics/DataTypeManagerPlugin/data_type_manager_description.ht
|
||||
src/main/help/help/topics/DataTypeManagerPlugin/data_type_manager_window.html||GHIDRA||||END|
|
||||
src/main/help/help/topics/DataTypeManagerPlugin/images/CommitDialog.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/DataTypeManagerPlugin/images/DataTypeManager.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/DataTypeManagerPlugin/images/DataTypeTreeWithAssociations.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/DataTypeManagerPlugin/images/DisassociateDialog.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/DataTypeManagerPlugin/images/EditPaths.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/DataTypeManagerPlugin/images/FavoriteDts.png||GHIDRA||||END|
|
||||
@@ -361,6 +362,7 @@ src/main/help/help/topics/DataTypeManagerPlugin/images/MergeErrorDialog.png||GHI
|
||||
src/main/help/help/topics/DataTypeManagerPlugin/images/PreviewWindow.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/DataTypeManagerPlugin/images/RevertDialog.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/DataTypeManagerPlugin/images/SearchResults.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/DataTypeManagerPlugin/images/StructureMergeDialog.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/DataTypeManagerPlugin/images/UpdateDialog.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/DataTypeManagerPlugin/images/lockoverlay.png||Nuvola Icons - LGPL 2.1|||Nuvola icon set|END|
|
||||
src/main/help/help/topics/DataTypePreviewPlugin/DataTypePreviewPlugin.html||GHIDRA||||END|
|
||||
|
||||
@@ -402,7 +402,7 @@ icon.base.mem.search.panel.scan = view_bottom.png
|
||||
icon.base.mem.search.panel.search = view_top_bottom.png
|
||||
|
||||
icon.base.plugin.quickfix.done = icon.checkmark.green
|
||||
|
||||
icon.base.merge.struct.apply = icon.checkmark.green[size(12,12)]
|
||||
|
||||
[Dark Defaults]
|
||||
|
||||
|
||||
@@ -169,7 +169,7 @@ color.bg.plugin.overview.entropy.palette.base.high = color.palette.white
|
||||
|
||||
color.bg.plugin.references.table.active.operand = color.palette.lightgray
|
||||
|
||||
color.bg.plugin.register.marker = color.palette.darkcyan
|
||||
color.bg.plugin.register.marker = color.palette.darkcyan
|
||||
|
||||
color.bg.plugin.windowlocation = color.palette.black
|
||||
color.bg.plugin.windowlocation.bounds.virtual = color.palette.red
|
||||
@@ -178,6 +178,8 @@ color.bg.plugin.windowlocation.screens = color.palette.orange
|
||||
color.bg.plugin.windowlocation.window.selected = color.palette.lime
|
||||
color.fg.plugin.windowlocation.window.text = color.palette.gray
|
||||
|
||||
color.bg.plugin.struct.merge.selection.non.focused = rgb(211, 211, 211)
|
||||
|
||||
|
||||
font.print = SansSerif-PLAIN-10
|
||||
font.splash.infopanel = SansSerif-BOLD-14
|
||||
@@ -215,5 +217,6 @@ font.textarea.astextfield = [laf.font]TextField.font
|
||||
color.bg.undefined = #3A2A48
|
||||
|
||||
color.fg.analysis.options.prototype.selected = color.palette.crimson
|
||||
color.bg.plugin.struct.merge.selection.non.focused = rgb(15, 42, 61)
|
||||
|
||||
|
||||
|
||||
+59
-11
@@ -1095,22 +1095,70 @@
|
||||
<H3><A name="MergeDataType"></A>Merging Data Types</H3>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>Some data types can be merged with others of the same type, provided there are no
|
||||
conflicting entries in the two data types. Currently,
|
||||
structures, unions and enums are supported for merging. If a conflict is detected,
|
||||
an error dialog will be displayed showing the two data types and a message explaining
|
||||
why the merge failed.</P>
|
||||
<P>Some data types can be merged with others of the same type. </P>
|
||||
<P>Currently, Ghidra supports
|
||||
merging structures, unions, and enums. Unions and enums have limited support in that you
|
||||
can only merge them if there are no conflicts and Ghidra can auto-merge them. For
|
||||
structures, Ghidra will launch an interactive structure merger dialog that allows users
|
||||
to pick and choose various elements from either structure.</P>
|
||||
<P>To Merge a data type, right-click on the type to be merged into and select the
|
||||
<B><I>Merge...</I></B> action. This will show a
|
||||
<A HREF="help/topics/DataTypeEditors/DataTypeSelectionDialog.htm">dialog</A> that allows
|
||||
you to choose the data type to merge with.
|
||||
</P>
|
||||
|
||||
<H4><A name="MergeStructures">Merging Structures</H4>
|
||||
<BLOCKQUOTE>
|
||||
<P>When merging structures, an interactive dialog will be displayed showing the two
|
||||
structures being merged along with a preview of the resulting merged structure.
|
||||
</BLOCKQUOTE>
|
||||
<P><CENTER>
|
||||
<IMG src="images/StructureMergeDialog.png" alt="" border="0"/>
|
||||
</CENTER></P>
|
||||
<BLOCKQUOTE>
|
||||
<P>In the upper half of the dialog, the two structures being merged are displayed side by
|
||||
side and aligned by offset. The lower section displays the resulting structure which changes
|
||||
as the user selects different components from the two structures being merged. All three
|
||||
structure views scroll together and selecting a line in any view, will also select
|
||||
the corresponding line in the other views.</P>
|
||||
<P>In between the the display of the two input structures is a button panel that displays
|
||||
buttons on any lines where there is a conflict and the user has a choice to select the
|
||||
values from the left or right side structure.</P>
|
||||
<P>The displays of the left/right input structures will have optional values bolded or
|
||||
faded, depending on if that value is currently chosen to be included in the resulting
|
||||
merged structure.</P>
|
||||
<P>Selecting a button will cause that side's corresponding values to be applied to the
|
||||
resulting structure and will cause any conflicting components from the other side to
|
||||
be removed.</P>
|
||||
<P>Buttons associated with structure component lines can also be deselected, clearing
|
||||
the corresponding component from the merged structure.</P>
|
||||
<P>The choices for the structure name and description always require one side or the
|
||||
other to be selected, but for component choices, it is possible also deselect a button,
|
||||
causing the resulting structure to not have a value from either side, instead reverting
|
||||
to undefined bytes.</P>
|
||||
<P>The dialog has several features to make it easier to control from just the keyboard.
|
||||
Using the tab and <shift>tab, the focus can be quickly moved between the left display,
|
||||
the right display, the merged display, apply button and the cancel button. In addition,
|
||||
you can quickly jump to the the left or right display by pressing the left or
|
||||
right arrow keys respectively. Also, you can apply/unapply a left or right side item by
|
||||
navigating to the item on the left or right side and pressing the [SPACE] key. </P>
|
||||
<P>Also, you can quickly give focus to the left or right display, by pressing the
|
||||
[LEFT ARROW] or [RIGHT ARROW] keys respectively. </P>
|
||||
<P>When the resulting structure has been adjusted to the desired result, press the
|
||||
<B>Apply</B> button to perform the merge.</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H4>Merging Enums and Unions</H4>
|
||||
<BLOCKQUOTE>
|
||||
<P>Merging Enums and Unions is a simple pass/fail action. Generally, enums and unions
|
||||
will merge without conflicts unless one or more elements have the same name for different
|
||||
values/datatypes.</P>
|
||||
<P>If the merge succeeds in producing a merged data type result, a confirmation dialog
|
||||
will be displayed showing the result and the two datatypes being merged. If the user
|
||||
confirms the merge, the original target data type will have its internals replaced
|
||||
with the merged data type and the other data type will have all its references replaced
|
||||
with the resulting datatype and then it will be deleted.
|
||||
</P>
|
||||
<P>To Merge a data type, right-click on the type to be merged into and select the
|
||||
<B><I>Merge...</I></B> action. This will show a
|
||||
<A HREF="help/topics/DataTypeEditors/DataTypeSelectionDialog.htm">dialog</A> that allows
|
||||
you to choose the data type to merge with.
|
||||
</P>
|
||||
<A name="Merge_Confirmation"></A><P>If the merge succeeds in producing a merged data type,
|
||||
the following confirmation dialog will be displayed before the changes are actually
|
||||
applied.</P>
|
||||
@@ -1123,7 +1171,6 @@
|
||||
with the merge. If the user presses the apply button, the first data type will be updated
|
||||
to match the preview data type and the second data type will be deleted with all of its
|
||||
uses replaced with the updated first data type.</P>
|
||||
|
||||
<A name="Merge_Error"></A>
|
||||
<P>If the merge fails, the following error dialog is displayed showing a side-by-side
|
||||
view of the two data types that couldn't be merged, along with a description of the error
|
||||
@@ -1134,6 +1181,7 @@
|
||||
</CENTER></P>
|
||||
<BR><BR><BR>
|
||||
</BLOCKQUOTE>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H3><A name="Favorites"></A>Setting Favorite Data Types</H3>
|
||||
|
||||
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 11 KiB |
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 8.4 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
@@ -0,0 +1,181 @@
|
||||
/* ###
|
||||
* 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.merge.structures;
|
||||
|
||||
/**
|
||||
* Base class for items that can be displayed in a {@link CoordinatedStructureDisplay}. These are
|
||||
* basically different views from a {@link CoordinatedStructureLine} where each coordinated line
|
||||
* has three comparison items, one for the left side structure, one for the right side structure,
|
||||
* and for the merged structure.
|
||||
*/
|
||||
public abstract class ComparisonItem implements Comparable<ComparisonItem> {
|
||||
// the max number of display columns any ComparisonItem can have
|
||||
public static int MAX_COLS = 5;
|
||||
|
||||
enum ItemApplyState {
|
||||
NON_APPLIALBLE, APPLIED, NOT_APPLIED
|
||||
}
|
||||
|
||||
private int line;
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param type The type of comparison item (name, comment, component, etc.)
|
||||
* @param line the line number in the overall coordinated structure model
|
||||
*/
|
||||
ComparisonItem(String type, int line) {
|
||||
this.type = type;
|
||||
this.line = line;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the text to be display for the given column index.
|
||||
* @param column the index of the column to get text for
|
||||
* @return the text to be display for the given column index.
|
||||
*/
|
||||
public String getColumnText(int column) {
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this items represents something that can be applied or not applied. Used
|
||||
* to determine if a button should be created for this item. For
|
||||
* example, a structure name can applied from either side, but the simple syntax line "{"
|
||||
* is the same for all sides, so it is never appliable and should not have a corresponding
|
||||
* button.
|
||||
* @return true if this item can potentially be applied.
|
||||
*/
|
||||
public boolean isAppliable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if any information in this item is not currently applied to the merged item.
|
||||
* if true, the button should be displayed as unselected, indicating to the user that this
|
||||
* item has information that can be applied.
|
||||
* @return true if any information in this item can be applied.
|
||||
*/
|
||||
public boolean canApplyAny() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the information from this item can be cleared. Currently only items
|
||||
* from component lines can be cleared. Items such as the structure name can never be cleared
|
||||
* and can only change by selecting the other side value.
|
||||
* If true, the button will be allowed to become unselected without the other side being
|
||||
* selected.
|
||||
* @return true if the information from this item can be cleared
|
||||
*/
|
||||
public boolean canClear() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the specific information represented by the given column index is applied.
|
||||
* This is used by the renderer to bold information that is applied and fade information
|
||||
* that is not applied.
|
||||
* @param column the column index to check if it is applied
|
||||
* @return true if this column information is currently applied
|
||||
*/
|
||||
public boolean isApplied(int column) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the specific information represented by the given column index is something
|
||||
* can be applied whether or not it is currently applied. This is used by the renderer to
|
||||
* render this columns test normally (not faded or bold)
|
||||
* @param column the column index to check if it is appliable
|
||||
* @return true if this columns information is changeable
|
||||
*/
|
||||
public boolean isAppliable(int column) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the minimum width of this column. Used to reserve space for a column even when
|
||||
* there is no text to display in the column. The column may be wider if its text is wider
|
||||
* than the minimum width. Used to help the renderer allocate available extra space when
|
||||
* the view is resized.
|
||||
* @param column the column to get the min width for
|
||||
* @return the minimum width of this column
|
||||
*/
|
||||
public int getMinWidth(int column) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies if the column text should be left or right justified within it column.
|
||||
* @param column the column index
|
||||
* @return true if the column text should be justified to the left side of the column
|
||||
*/
|
||||
public boolean isLeftJustified(int column) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies all the information in this item to the merged structure.
|
||||
*/
|
||||
public void applyAll() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears this item from the merged structure. Normally items from one side or the other
|
||||
* are cleared when the corresponding item for the other side is applied. This allows the
|
||||
* state where neither side is applied.
|
||||
*/
|
||||
public void clear() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if this item represent a blank line. Useful for removing blank lines from the
|
||||
* merge structure view.
|
||||
* @return true if this item represents a blank line
|
||||
*/
|
||||
public boolean isBlank() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the line number for this item in the coordinated display. Note that this may
|
||||
* be different from its index in its list model. The merged display removes blank lines, but
|
||||
* maintains the line number where it matches in the left/right displays. This is uses to
|
||||
* coordinated the left/right/merged views.
|
||||
* @return the line number for this item
|
||||
*/
|
||||
public int getLine() {
|
||||
return line;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type for this item. Used to align the columns of like items. For example,
|
||||
* all the fields in the component lines must line up, but that alignment is not coordinated
|
||||
* with other categories such as the structure name line.
|
||||
* @return the category for this item.
|
||||
*/
|
||||
protected String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(ComparisonItem o) {
|
||||
return line - o.line;
|
||||
}
|
||||
}
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
/* ###
|
||||
* 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.merge.structures;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
/**
|
||||
* LayoutManager for arranging the labels for each column in a {@link ComparisonItem}.
|
||||
* The main idea here is that each type of item has a set of min/max widths associated with
|
||||
* each column that is used to align like types of items so that their fields line up.
|
||||
* <P> The tricky part is how to handle sizing them as the view is expanded or contracted.
|
||||
* Initially, all columns are given their minimum width and if the total is greater then the
|
||||
* available width, the last columns are clipped. If the available width is greater than the
|
||||
* sum of the minimum widths, the extra width (10 at a time) is given to each column that still has
|
||||
* text wider than its current width. This is repeated until the extra width is used up or all
|
||||
* columns have all the width they need to display their text.
|
||||
*
|
||||
*/
|
||||
public class ComparisonItemLayout implements LayoutManager {
|
||||
private static int HGAP = 5;
|
||||
private FontMetrics metrics;
|
||||
|
||||
private ColumnWidths minMaxWidths = new ColumnWidths();
|
||||
private int[] adjustedWidths = new int[ComparisonItem.MAX_COLS];
|
||||
|
||||
@Override
|
||||
public void addLayoutComponent(String name, Component comp) {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeLayoutComponent(Component comp) {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension preferredLayoutSize(Container parent) {
|
||||
return minimumLayoutSize(parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension minimumLayoutSize(Container parent) {
|
||||
int n = parent.getComponentCount();
|
||||
int width = 0;
|
||||
for (int i = 0; i < n; i++) {
|
||||
width += minMaxWidths.getMinWidth(i);
|
||||
}
|
||||
Insets insets = parent.getInsets();
|
||||
return new Dimension(width + insets.left + insets.right + 3 * HGAP, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void layoutContainer(Container parent) {
|
||||
int n = parent.getComponentCount();
|
||||
Dimension d = parent.getSize();
|
||||
Insets insets = parent.getInsets();
|
||||
int width = d.width - insets.left - insets.right - 3 * HGAP;
|
||||
int height = d.height - insets.top - insets.bottom;
|
||||
computeWidths(width, n);
|
||||
|
||||
int x = 0;
|
||||
int widthSoFar = 0;
|
||||
for (int i = 0; i < n; i++) {
|
||||
int compWidth = Math.min(adjustedWidths[i], width - widthSoFar);
|
||||
Component c = parent.getComponent(i);
|
||||
c.setBounds(x, 0, compWidth, height);
|
||||
x += compWidth + HGAP;
|
||||
widthSoFar += compWidth;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void computeWidths(int width, int componentCount) {
|
||||
int totalWidth = 0;
|
||||
int totalMaxWidth = 0;
|
||||
for (int i = 0; i < componentCount; i++) {
|
||||
int min = minMaxWidths.getMinWidth(i);
|
||||
int max = minMaxWidths.getMaxWidth(i);
|
||||
totalWidth += min;
|
||||
totalMaxWidth += max;
|
||||
adjustedWidths[i] = min; // initialize columns widths to min size
|
||||
}
|
||||
|
||||
if (width >= totalMaxWidth) {
|
||||
// set all columns to max
|
||||
for (int i = 0; i < componentCount; i++) {
|
||||
adjustedWidths[i] = minMaxWidths.getMaxWidth(i);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// otherwise distribute extra width to those columns currently less than their max
|
||||
while (totalWidth < width && totalWidth < totalMaxWidth) {
|
||||
totalWidth = addToColumnWidths(totalMaxWidth - totalWidth, componentCount, metrics);
|
||||
}
|
||||
}
|
||||
|
||||
private int addToColumnWidths(int extraWidth, int componentCount, FontMetrics metrics) {
|
||||
int totalWidth = 0;
|
||||
for (int i = 0; i < ComparisonItem.MAX_COLS; i++) {
|
||||
int maxWidth = minMaxWidths.getMaxWidth(i);
|
||||
int incrementAmount = Math.min(maxWidth - adjustedWidths[i], 10);
|
||||
adjustedWidths[i] += incrementAmount;
|
||||
extraWidth -= incrementAmount;
|
||||
totalWidth += adjustedWidths[i];
|
||||
if (extraWidth <= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return totalWidth;
|
||||
}
|
||||
|
||||
public void setColumnWidths(ColumnWidths widths) {
|
||||
this.minMaxWidths = widths;
|
||||
}
|
||||
|
||||
static class ColumnWidths {
|
||||
private int[] minWidths = new int[ComparisonItem.MAX_COLS];
|
||||
private int[] maxWidths = new int[ComparisonItem.MAX_COLS];
|
||||
|
||||
int getMinWidth(int column) {
|
||||
return minWidths[column];
|
||||
}
|
||||
|
||||
int getMaxWidth(int column) {
|
||||
return maxWidths[column];
|
||||
}
|
||||
|
||||
void addMinWidth(int column, int width) {
|
||||
minWidths[column] = Math.max(minWidths[column], width);
|
||||
}
|
||||
|
||||
void addMaxWidth(int column, int width) {
|
||||
maxWidths[column] = Math.max(maxWidths[column], width);
|
||||
}
|
||||
}
|
||||
}
|
||||
+191
@@ -0,0 +1,191 @@
|
||||
/* ###
|
||||
* 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.merge.structures;
|
||||
|
||||
import static javax.swing.SwingConstants.*;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.ListDataEvent;
|
||||
import javax.swing.event.ListDataListener;
|
||||
|
||||
import generic.theme.GColor;
|
||||
import generic.theme.GThemeDefaults.Colors;
|
||||
import ghidra.app.merge.structures.ComparisonItemLayout.ColumnWidths;
|
||||
|
||||
/**
|
||||
* ListCellRenderer for rendering structure lines in {@link CoordinatedStructureDisplay}. It
|
||||
* consists of a label for each possible column. The labels are arranged by a
|
||||
* {@link ComparisonItemLayout} which is fed column widths for each type of line item. The
|
||||
* columns widths for each type are computed when the renderer is constructed by examining all
|
||||
* the lines in the model and computing the min/max column widths for that type.
|
||||
*/
|
||||
public class ComparisonItemRenderer implements ListCellRenderer<ComparisonItem>, ListDataListener {
|
||||
private static Color FADED_COLOR = Colors.FOREGROUND_DISABLED;
|
||||
private static Color NON_FOCUSED_SELECTION_BG_COLOR =
|
||||
new GColor("color.bg.plugin.struct.merge.selection.non.focused");
|
||||
|
||||
private JPanel panel;
|
||||
private JLabel[] labels = new JLabel[ComparisonItem.MAX_COLS];
|
||||
private int lineHeight;
|
||||
private ComparisonItemLayout layout;
|
||||
private Font normal;
|
||||
private Font bold;
|
||||
|
||||
private FontMetrics metrics;
|
||||
|
||||
private Map<String, ColumnWidths> widthsMap;
|
||||
|
||||
ComparisonItemRenderer(ListModel<ComparisonItem> listModel) {
|
||||
panel = new JPanel();
|
||||
for (int i = 0; i < ComparisonItem.MAX_COLS; i++) {
|
||||
labels[i] = new JLabel();
|
||||
labels[i].setHorizontalAlignment(SwingConstants.LEFT);
|
||||
panel.add(labels[i]);
|
||||
}
|
||||
normal = labels[0].getFont();
|
||||
bold = normal.deriveFont(Font.BOLD);
|
||||
metrics = labels[0].getFontMetrics(bold);
|
||||
lineHeight = metrics.getHeight();
|
||||
layout = new ComparisonItemLayout();
|
||||
panel.setLayout(layout);
|
||||
computeColumnWidths(listModel);
|
||||
listModel.addListDataListener(this);
|
||||
}
|
||||
|
||||
private void computeColumnWidths(ListModel<ComparisonItem> listModel) {
|
||||
widthsMap = new HashMap<>();
|
||||
for (int i = 0; i < listModel.getSize(); i++) {
|
||||
ComparisonItem item = listModel.getElementAt(i);
|
||||
ColumnWidths widths =
|
||||
widthsMap.computeIfAbsent(item.getType(), k -> new ColumnWidths());
|
||||
for (int col = 0; col < ComparisonItem.MAX_COLS; col++) {
|
||||
int maxWidth = metrics.stringWidth(item.getColumnText(col));
|
||||
int minWidth = item.getMinWidth(col);
|
||||
if (minWidth < 0) {
|
||||
minWidth = maxWidth;
|
||||
}
|
||||
widths.addMinWidth(col, minWidth);
|
||||
widths.addMaxWidth(col, maxWidth);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public FontMetrics getFontMetrics() {
|
||||
return metrics;
|
||||
}
|
||||
|
||||
public int getPreferredHeight() {
|
||||
return lineHeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getListCellRendererComponent(JList<? extends ComparisonItem> list,
|
||||
ComparisonItem value, int index, boolean isSelected, boolean cellHasFocus) {
|
||||
layout.setColumnWidths(widthsMap.get(value.getType()));
|
||||
boolean hasFocus = list.hasFocus();
|
||||
// Note: Setting the accessible description on the renderer works and it
|
||||
// reports the correct information as you hover or select lines in the list
|
||||
panel.getAccessibleContext().setAccessibleName(value.getType() + " line");
|
||||
panel.getAccessibleContext().setAccessibleDescription(getAccessibleDescription(value));
|
||||
Color bgColor = getBackgroundColor(list, isSelected, hasFocus);
|
||||
Color fgColor = getForegroundColor(list, isSelected, hasFocus);
|
||||
Color fadedColor = getFadedColor(fgColor, isSelected, hasFocus);
|
||||
for (int i = 0; i < ComparisonItem.MAX_COLS; i++) {
|
||||
labels[i].setText(value.getColumnText(i));
|
||||
labels[i].setHorizontalAlignment(value.isLeftJustified(i) ? LEFT : RIGHT);
|
||||
if (!value.isAppliable(i)) {
|
||||
labels[i].setFont(normal);
|
||||
labels[i].setForeground(fgColor);
|
||||
}
|
||||
else if (value.isApplied(i)) {
|
||||
labels[i].setFont(bold);
|
||||
labels[i].setForeground(fgColor);
|
||||
}
|
||||
else {
|
||||
labels[i].setFont(normal);
|
||||
labels[i].setForeground(fadedColor);
|
||||
}
|
||||
}
|
||||
|
||||
panel.setBackground(bgColor);
|
||||
return panel;
|
||||
}
|
||||
|
||||
private Color getBackgroundColor(JList<? extends ComparisonItem> list, boolean isSelected,
|
||||
boolean hasFocus) {
|
||||
if (!isSelected) {
|
||||
return list.getBackground();
|
||||
}
|
||||
if (hasFocus) {
|
||||
return list.getSelectionBackground();
|
||||
}
|
||||
return NON_FOCUSED_SELECTION_BG_COLOR;
|
||||
}
|
||||
|
||||
private Color getForegroundColor(JList<? extends ComparisonItem> list, boolean isSelected,
|
||||
boolean hasFocus) {
|
||||
if (hasFocus && isSelected) {
|
||||
return list.getSelectionForeground();
|
||||
}
|
||||
return list.getForeground();
|
||||
}
|
||||
|
||||
private String getAccessibleDescription(ComparisonItem value) {
|
||||
if (value.isBlank()) {
|
||||
return "Blank line";
|
||||
}
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append(value.toString());
|
||||
if (!value.isAppliable()) {
|
||||
builder.append(" Status: Not Appliable");
|
||||
}
|
||||
else if (value.canApplyAny()) {
|
||||
builder.append(" Status: Not Applied");
|
||||
}
|
||||
else {
|
||||
builder.append(" Status: Applied");
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private Color getFadedColor(Color fgColor, boolean isSelected, boolean hasFocus) {
|
||||
if (isSelected && !hasFocus) {
|
||||
return fgColor;
|
||||
}
|
||||
return FADED_COLOR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void intervalAdded(ListDataEvent e) {
|
||||
// don't care
|
||||
}
|
||||
|
||||
@Override
|
||||
public void intervalRemoved(ListDataEvent e) {
|
||||
// don't care
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void contentsChanged(ListDataEvent e) {
|
||||
computeColumnWidths((ListModel<ComparisonItem>) e.getSource());
|
||||
}
|
||||
|
||||
}
|
||||
+231
@@ -0,0 +1,231 @@
|
||||
/* ###
|
||||
* 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.merge.structures;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.Border;
|
||||
import javax.swing.event.ChangeListener;
|
||||
|
||||
import docking.actions.KeyBindingUtils;
|
||||
import generic.theme.GIcon;
|
||||
|
||||
/**
|
||||
* Class for displaying a view into a {@link CoordinatedStructureModel}, showing either the
|
||||
* left structure, the right structure, or the merged structure. It consists of a JList in
|
||||
* a JScrollpane where the list model is extracted from the {@link CoordinatedStructureModel} for
|
||||
* either the left,right, or merged view. These views track together for both view scrolling and
|
||||
* list selection. They all share a {@link DisplayCoordinator} that assists with coordinating the
|
||||
* views.
|
||||
*/
|
||||
public class CoordinatedStructureDisplay extends JPanel {
|
||||
public static final int MARGIN = 10;
|
||||
|
||||
static Icon APPLY_ICON = new GIcon("icon.base.merge.struct.apply");
|
||||
|
||||
private JList<ComparisonItem> jList;
|
||||
private JScrollPane scroll;
|
||||
private JScrollBar horizontalScrollbar;
|
||||
private JScrollBar verticalScrollbar;
|
||||
|
||||
private String title;
|
||||
private StructDisplayModel listModel;
|
||||
private int rowHeight;
|
||||
|
||||
private ComparisonItemRenderer renderer;
|
||||
|
||||
private DisplayCoordinator coordinator;
|
||||
|
||||
public CoordinatedStructureDisplay(String title, StructDisplayModel listModel,
|
||||
DisplayCoordinator coordinator) {
|
||||
super(new BorderLayout());
|
||||
this.title = title;
|
||||
this.listModel = listModel;
|
||||
this.coordinator = coordinator;
|
||||
Border emptyBorder = BorderFactory.createEmptyBorder(10, 0, 0, 0);
|
||||
setBorder(BorderFactory.createTitledBorder(emptyBorder, title));
|
||||
renderer = new ComparisonItemRenderer(listModel);
|
||||
jList = new JList<ComparisonItem>(listModel);
|
||||
rowHeight = Math.max(renderer.getPreferredHeight(), APPLY_ICON.getIconHeight() + 6);
|
||||
jList.setFixedCellHeight(rowHeight);
|
||||
jList.setCellRenderer(renderer);
|
||||
jList.setBorder(
|
||||
BorderFactory.createEmptyBorder(MARGIN, MARGIN, MARGIN, MARGIN));
|
||||
jList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||
|
||||
// remove key bindings so we can assign them to our actions
|
||||
clearBinding("SPACE");
|
||||
clearBinding("LEFT");
|
||||
clearBinding("RIGHT");
|
||||
|
||||
scroll = new JScrollPane(jList);
|
||||
horizontalScrollbar = scroll.getHorizontalScrollBar();
|
||||
verticalScrollbar = scroll.getVerticalScrollBar();
|
||||
add(scroll);
|
||||
jList.addListSelectionListener(e -> notifyCoordiatorSelectionChanged());
|
||||
horizontalScrollbar
|
||||
.addAdjustmentListener(e -> coordinator.notifyHorizontalScrollChanged(this, e));
|
||||
verticalScrollbar
|
||||
.addAdjustmentListener(e -> coordinator.notifyVerticalScrollChanged(this, e));
|
||||
coordinator.registerDisplay(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the list item to be selected based on the the selection change of the given display.
|
||||
* @param changedDisplay the display that was changed by the user and is being used to update
|
||||
* the other displays.
|
||||
* @param index the list index that was selected in the given display.
|
||||
* @param item the comparison item that was selected the the given display.
|
||||
*/
|
||||
void setSelectedItem(CoordinatedStructureDisplay changedDisplay, int index,
|
||||
ComparisonItem item) {
|
||||
if (changedDisplay == this) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the models have different sizes, find the index of the corresponding item using
|
||||
// the line number from the item. Otherwise, the list index can be used directly.
|
||||
if (changedDisplay.getItemCount() != getItemCount()) {
|
||||
index = listModel.getIndex(item);
|
||||
}
|
||||
|
||||
if (index < 0) {
|
||||
jList.clearSelection();
|
||||
}
|
||||
else {
|
||||
jList.setSelectedIndex(index);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the horizontal scroll position based on the view change in one of the other displays.
|
||||
* @param changedDisplay the display that was changed by the user.
|
||||
* @param value the horizontal scroll position of the given display.
|
||||
*/
|
||||
void setHorizontalScroll(CoordinatedStructureDisplay changedDisplay, int value) {
|
||||
if (changedDisplay == this) {
|
||||
return;
|
||||
}
|
||||
horizontalScrollbar.setValue(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the vertical scroll position based on the view change in one of the other displays.
|
||||
* @param changedDisplay the display that was changed by the user. Coordinating the vertical
|
||||
* position can be tricky because the merged display has had its blank lines removed. If the
|
||||
* model sizes are different, it uses the line number information in the displayed items to
|
||||
* compute the corresponding position.
|
||||
* @param value the vertical scroll position of the given display.
|
||||
*/
|
||||
void setVerticalScroll(CoordinatedStructureDisplay changedDisplay, int value) {
|
||||
if (this == changedDisplay) {
|
||||
return;
|
||||
}
|
||||
if (getItemCount() == changedDisplay.getItemCount()) {
|
||||
// If the models are the same size, we can use the vertical scroll position directly
|
||||
verticalScrollbar.setValue(value);
|
||||
}
|
||||
else {
|
||||
// Otherwise, we have to find the corresponding first object in the other display.
|
||||
int idx = changedDisplay.getFirstVisibleIndex();
|
||||
if (idx < 0) {
|
||||
return;
|
||||
}
|
||||
Rectangle r = changedDisplay.jList.getCellBounds(idx, idx);
|
||||
ComparisonItem first = changedDisplay.getFirstVisibleItem();
|
||||
|
||||
// We also compute a vertical line offset so that if the first item has a match and
|
||||
// is partially scrolled off the screen, we can partially scroll this display to match.
|
||||
int offset = r.y - value;
|
||||
|
||||
int index = listModel.getIndex(first);
|
||||
if (index < 0) {
|
||||
offset = 0; // don't partial scroll for inexact match
|
||||
index = -index - 1;
|
||||
}
|
||||
Rectangle cellBounds = jList.getCellBounds(index, index);
|
||||
verticalScrollbar.setValue(cellBounds.y - offset);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ComparisonItem getItem(int index) {
|
||||
return listModel.getElementAt(index);
|
||||
}
|
||||
|
||||
int getRowHeight() {
|
||||
return rowHeight;
|
||||
}
|
||||
|
||||
void setRowHeight(int rowHeight) {
|
||||
this.rowHeight = rowHeight;
|
||||
jList.setFixedCellHeight(rowHeight);
|
||||
}
|
||||
|
||||
int getFirstVisibleIndex() {
|
||||
return jList.getFirstVisibleIndex();
|
||||
}
|
||||
|
||||
int getLastVisibleIndex() {
|
||||
return jList.getLastVisibleIndex();
|
||||
}
|
||||
|
||||
ComparisonItem getSelectedItem() {
|
||||
return jList.getSelectedValue();
|
||||
}
|
||||
|
||||
Component getList() {
|
||||
return jList;
|
||||
}
|
||||
|
||||
void addViewportListener(ChangeListener l) {
|
||||
scroll.getViewport().addChangeListener(l);
|
||||
}
|
||||
|
||||
private int getItemCount() {
|
||||
return listModel.getSize();
|
||||
}
|
||||
|
||||
private void clearBinding(String keyName) {
|
||||
KeyBindingUtils.clearKeyBinding(jList, KeyBindingUtils.parseKeyStroke(keyName));
|
||||
KeyBindingUtils.clearKeyBinding(jList, KeyBindingUtils.parseKeyStroke("ctrl " + keyName));
|
||||
KeyBindingUtils.clearKeyBinding(jList, KeyBindingUtils.parseKeyStroke("shift " + keyName));
|
||||
KeyBindingUtils.clearKeyBinding(jList,
|
||||
KeyBindingUtils.parseKeyStroke("ctrl shift " + keyName));
|
||||
}
|
||||
|
||||
private void notifyCoordiatorSelectionChanged() {
|
||||
int selectedIndex = jList.getSelectedIndex();
|
||||
ComparisonItem item = jList.getSelectedValue();
|
||||
coordinator.notifySelectionChanged(this, selectedIndex, item);
|
||||
|
||||
}
|
||||
|
||||
private ComparisonItem getFirstVisibleItem() {
|
||||
int index = jList.getFirstVisibleIndex();
|
||||
if (index >= 0) {
|
||||
return listModel.getElementAt(index);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
/* ###
|
||||
* 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.merge.structures;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Base class for coordinating display lines of a left, right, and merged structure.
|
||||
*/
|
||||
public abstract class CoordinatedStructureLine {
|
||||
protected ComparisonItem left;
|
||||
protected ComparisonItem right;
|
||||
protected ComparisonItem merged;
|
||||
protected CoordinatedStructureModel model;
|
||||
|
||||
public enum CompareId {
|
||||
LEFT, RIGHT, MERGED;
|
||||
}
|
||||
|
||||
CoordinatedStructureLine(CoordinatedStructureModel model) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns either the left, right, or merged comparison item for this line.
|
||||
* @param id the id for which comparison item to return
|
||||
* @return either the left, right, or merged comparison item for this line
|
||||
*/
|
||||
ComparisonItem getComparisonItem(CompareId id) {
|
||||
switch (id) {
|
||||
case LEFT:
|
||||
return left;
|
||||
case RIGHT:
|
||||
return right;
|
||||
case MERGED:
|
||||
default:
|
||||
return merged;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
CoordinatedStructureLine other = (CoordinatedStructureLine) obj;
|
||||
return Objects.equals(left, other.left) &&
|
||||
Objects.equals(right, other.right) &&
|
||||
Objects.equals(merged, other.merged);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(left, right, merged);
|
||||
}
|
||||
|
||||
protected void modelChanged() {
|
||||
model.rebuild();
|
||||
}
|
||||
|
||||
protected void error(String errorMessage) {
|
||||
model.error(errorMessage);
|
||||
}
|
||||
}
|
||||
+474
@@ -0,0 +1,474 @@
|
||||
/* ###
|
||||
* 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.merge.structures;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import ghidra.app.merge.structures.CoordinatedStructureLine.CompareId;
|
||||
import ghidra.program.database.data.merge.DataTypeMergeException;
|
||||
import ghidra.program.database.data.merge.StructureMerger;
|
||||
import ghidra.program.model.data.*;
|
||||
import util.CollectionUtils;
|
||||
import utility.function.Callback;
|
||||
|
||||
/**
|
||||
* Model for merging two structures in an interactive dialog. The first structure will be considered
|
||||
* the left structure (it will be displayed on the left side) and the second structure will be
|
||||
* considered the right structure. This class will internally generate a merged structure by
|
||||
* combining the two given structures. Initially, if there is a conflict, the first structure (left)
|
||||
* will be given precedence.
|
||||
*/
|
||||
public class CoordinatedStructureModel {
|
||||
|
||||
private Structure leftStruct;
|
||||
private Structure rightStruct;
|
||||
private Structure mergedStruct;
|
||||
private List<CoordinatedStructureLine> compareLines;
|
||||
private Consumer<String> errorHandler;
|
||||
private List<Callback> changeCallbacks = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param struct1 the left structure (has initial precedence for conflicts)
|
||||
* @param struct2 the right structure
|
||||
* @param errorHandler a consumer for reporting errors
|
||||
*/
|
||||
public CoordinatedStructureModel(Structure struct1, Structure struct2,
|
||||
Consumer<String> errorHandler) {
|
||||
this.leftStruct = struct1;
|
||||
this.errorHandler = errorHandler;
|
||||
|
||||
// make sure struct2 has same data organization as struct1
|
||||
this.rightStruct = struct2.clone(struct1.getDataTypeManager());
|
||||
StructureMerger merger = new StructureMerger(struct1, struct2, false);
|
||||
try {
|
||||
this.mergedStruct = merger.merge();
|
||||
}
|
||||
catch (DataTypeMergeException e) {
|
||||
this.mergedStruct = (Structure) struct1.copy(struct1.getDataTypeManager());
|
||||
}
|
||||
|
||||
compareLines = buildLines();
|
||||
}
|
||||
|
||||
/**
|
||||
* Completely rebuilds all the structure compare lines. Called whenever any change is made to
|
||||
* the merged structure.
|
||||
*/
|
||||
void rebuild() {
|
||||
compareLines = buildLines();
|
||||
for (Callback callback : changeCallbacks) {
|
||||
callback.call();
|
||||
}
|
||||
}
|
||||
|
||||
private List<CoordinatedStructureLine> buildLines() {
|
||||
List<CoordinatedStructureLine> list = new ArrayList<>();
|
||||
LineBuilder lineBuilder = new LineBuilder(list);
|
||||
|
||||
String description1 = leftStruct.getDescription();
|
||||
String description2 = rightStruct.getDescription();
|
||||
if (!StringUtils.isBlank(description1) || !StringUtils.isBlank(description2)) {
|
||||
list.add(new StructureDescriptionLine(this, leftStruct, rightStruct, mergedStruct,
|
||||
list.size()));
|
||||
}
|
||||
|
||||
list.add(new StructureNameLine(this, leftStruct, rightStruct, mergedStruct, list.size()));
|
||||
list.add(new StructureInfoLine(this, getInfo(leftStruct), getInfo(rightStruct),
|
||||
getInfo(mergedStruct), list.size(), "Structure properties"));
|
||||
list.add(new StructureInfoLine(this, "{", list.size(), "Syntax"));
|
||||
|
||||
lineBuilder.addComponentLines();
|
||||
|
||||
list.add(new StructureInfoLine(this, "}", list.size(), "Syntax"));
|
||||
return list;
|
||||
}
|
||||
|
||||
private String getInfo(Structure s) {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append("Length = ");
|
||||
int length = s.getLength();
|
||||
buf.append(length);
|
||||
buf.append(" (0x");
|
||||
buf.append(Integer.toString(length, 16));
|
||||
buf.append("), alignment = ");
|
||||
buf.append(s.getAlignment());
|
||||
buf.append(", Packed = ");
|
||||
buf.append(s.isPackingEnabled());
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
public int getSize() {
|
||||
return compareLines.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Inner class to build the coordinated component lines (the hard part) of the three structures.
|
||||
*/
|
||||
private class LineBuilder {
|
||||
private List<CoordinatedStructureLine> lines;
|
||||
private StructureComponentLine lastComponentLine;
|
||||
private DefinedComponentQueue q1 = new DefinedComponentQueue(leftStruct);
|
||||
private DefinedComponentQueue q2 = new DefinedComponentQueue(rightStruct);
|
||||
private DefinedComponentQueue q3 = new DefinedComponentQueue(mergedStruct);
|
||||
|
||||
LineBuilder(List<CoordinatedStructureLine> lines) {
|
||||
this.lines = lines;
|
||||
}
|
||||
|
||||
void addComponentLines() {
|
||||
int offset = getNextOffset();
|
||||
while (offset >= 0) {
|
||||
processOffset(offset);
|
||||
offset = getNextOffset();
|
||||
}
|
||||
fillGaps(getMaxSize());
|
||||
}
|
||||
|
||||
private int getMaxSize() {
|
||||
int size = Math.max(leftStruct.getLength(), rightStruct.getLength());
|
||||
return Math.max(size, mergedStruct.getLength());
|
||||
}
|
||||
|
||||
private int getNextOffset() {
|
||||
int offset1 = q1.nextOffset();
|
||||
int offset2 = q2.nextOffset();
|
||||
int offset3 = q3.nextOffset();
|
||||
|
||||
int offset = getMinOffset(offset1, offset2);
|
||||
return getMinOffset(offset, offset3);
|
||||
}
|
||||
|
||||
private int getMinOffset(int offset1, int offset2) {
|
||||
if (offset1 < 0) {
|
||||
return offset2;
|
||||
}
|
||||
if (offset2 < 0) {
|
||||
return offset1;
|
||||
}
|
||||
return Math.min(offset1, offset2);
|
||||
}
|
||||
|
||||
private void processOffset(int offset) {
|
||||
fillGaps(offset);
|
||||
|
||||
if (q1.hasZeroComp(offset) || q2.hasZeroComp(offset) || q3.hasZeroComp(offset)) {
|
||||
processZeroLengthComponents(offset);
|
||||
}
|
||||
|
||||
if (q1.hasBitField(offset) || q2.hasBitField(offset) || q3.hasBitField(offset)) {
|
||||
processBitFields(offset);
|
||||
}
|
||||
|
||||
DataTypeComponent comp1 = q1.nextOffset() == offset ? q1.next() : null;
|
||||
DataTypeComponent comp2 = q2.nextOffset() == offset ? q2.next() : null;
|
||||
DataTypeComponent comp3 = q3.nextOffset() == offset ? q3.next() : null;
|
||||
if (CollectionUtils.isAllNull(comp1, comp2, comp3)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (comp1 == null) {
|
||||
// we already know that a defined component is not there, but this checks for
|
||||
// an undefined at that offset
|
||||
comp1 = getUndefinedComp(leftStruct, offset);
|
||||
}
|
||||
if (comp2 == null) {
|
||||
comp2 = getUndefinedComp(rightStruct, offset);
|
||||
}
|
||||
if (comp3 == null) {
|
||||
comp3 = getUndefinedComp(mergedStruct, offset);
|
||||
}
|
||||
int length = getMaxLength(comp1, comp2, comp3);
|
||||
lastComponentLine =
|
||||
new StructureComponentLine(CoordinatedStructureModel.this, leftStruct, rightStruct,
|
||||
mergedStruct, comp1, comp2, comp3, offset, length, lines.size());
|
||||
lines.add(lastComponentLine);
|
||||
}
|
||||
|
||||
private void processBitFields(int offset) {
|
||||
boolean isFirstBitFieldLine = true;
|
||||
DataTypeComponent bitField1 = q1.hasBitField(offset) ? q1.next() : null;
|
||||
DataTypeComponent bitField2 = q2.hasBitField(offset) ? q2.next() : null;
|
||||
|
||||
while (bitField1 != null || bitField2 != null) {
|
||||
int result = compareBitFields(bitField1, bitField2, offset);
|
||||
if (result == 0) {
|
||||
addBitFieldLine(bitField1, bitField2, offset, isFirstBitFieldLine);
|
||||
bitField1 = q1.hasBitField(offset) ? q1.next() : null;
|
||||
bitField2 = q2.hasBitField(offset) ? q2.next() : null;
|
||||
}
|
||||
else if (result < 0) {
|
||||
addBitFieldLine(bitField1, null, offset, isFirstBitFieldLine);
|
||||
bitField1 = q1.hasBitField(offset) ? q1.next() : null;
|
||||
}
|
||||
else {
|
||||
addBitFieldLine(null, bitField2, offset, isFirstBitFieldLine);
|
||||
bitField2 = q2.hasBitField(offset) ? q2.next() : null;
|
||||
}
|
||||
isFirstBitFieldLine = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void addBitFieldLine(DataTypeComponent leftComp, DataTypeComponent rightComp,
|
||||
int offset, boolean isFirstBitFieldLineForOffset) {
|
||||
DataTypeComponent mergedComp = null;
|
||||
DataTypeComponent comp = leftComp != null ? leftComp : rightComp;
|
||||
int bitFieldLength1 = leftComp != null ? leftComp.getLength() : 0;
|
||||
int bitFieldLength2 = rightComp != null ? rightComp.getLength() : 0;
|
||||
int length = Math.max(bitFieldLength1, bitFieldLength2);
|
||||
|
||||
if (q3.hasBitField(offset)) {
|
||||
// peek at the next component in the result struct and see if it is a bitfield
|
||||
// that matches the entry we are creating
|
||||
DataTypeComponent peek = q3.peek();
|
||||
if (compareBitFields(comp, peek, offset) == 0) {
|
||||
mergedComp = q3.next();
|
||||
}
|
||||
}
|
||||
|
||||
// If this is the 1st bit field line for an offset, add in undefined components
|
||||
// if appropriate for null components
|
||||
if (isFirstBitFieldLineForOffset) {
|
||||
if (leftComp == null) {
|
||||
leftComp = getUndefinedComp(leftStruct, offset);
|
||||
}
|
||||
if (rightComp == null) {
|
||||
rightComp = getUndefinedComp(rightStruct, offset);
|
||||
}
|
||||
if (mergedComp == null) {
|
||||
mergedComp = getUndefinedComp(mergedStruct, offset);
|
||||
}
|
||||
}
|
||||
lastComponentLine =
|
||||
new StructureComponentLine(CoordinatedStructureModel.this, leftStruct, rightStruct,
|
||||
mergedStruct,
|
||||
leftComp, rightComp, mergedComp, offset, length, lines.size());
|
||||
lines.add(lastComponentLine);
|
||||
|
||||
}
|
||||
|
||||
private int compareBitFields(DataTypeComponent bitField1, DataTypeComponent bitField2,
|
||||
int offset) {
|
||||
if (bitField1 == null) {
|
||||
return 1;
|
||||
}
|
||||
if (bitField2 == null) {
|
||||
return -1;
|
||||
}
|
||||
BitFieldDataType bfdt1 = (BitFieldDataType) bitField1.getDataType();
|
||||
BitFieldDataType bfdt2 = (BitFieldDataType) bitField2.getDataType();
|
||||
int bitStart1 = BitFieldDataType.getNormalizedBitOffset(bfdt1, offset);
|
||||
int bitStart2 = BitFieldDataType.getNormalizedBitOffset(bfdt2, offset);
|
||||
|
||||
return bitStart1 - bitStart2;
|
||||
}
|
||||
|
||||
private void processZeroLengthComponents(int offset) {
|
||||
List<DataTypeComponent> comps1 = getZeroLengthComps(q1, offset);
|
||||
List<DataTypeComponent> comps2 = getZeroLengthComps(q2, offset);
|
||||
List<DataTypeComponent> comps3 = getZeroLengthComps(q3, offset);
|
||||
|
||||
for (DataTypeComponent comp1 : comps1) {
|
||||
DataTypeComponent comp2 = findSameComp(comps2, comp1);
|
||||
DataTypeComponent comp3 = findSameComp(comps3, comp1);
|
||||
lastComponentLine =
|
||||
new StructureComponentLine(CoordinatedStructureModel.this, leftStruct,
|
||||
rightStruct, mergedStruct, comp1, comp2, comp3, offset, 0, lines.size());
|
||||
lines.add(lastComponentLine);
|
||||
}
|
||||
for (DataTypeComponent comp2 : comps2) {
|
||||
DataTypeComponent comp3 = findSameComp(comps3, comp2);
|
||||
lastComponentLine =
|
||||
new StructureComponentLine(CoordinatedStructureModel.this, leftStruct,
|
||||
rightStruct, mergedStruct, null, comp2, comp3, offset, 0, lines.size());
|
||||
lines.add(lastComponentLine);
|
||||
}
|
||||
for (DataTypeComponent comp3 : comps3) {
|
||||
lastComponentLine =
|
||||
new StructureComponentLine(CoordinatedStructureModel.this, leftStruct,
|
||||
rightStruct, mergedStruct, null, null, comp3, offset, 0, lines.size());
|
||||
lines.add(lastComponentLine);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private DataTypeComponent findSameComp(List<DataTypeComponent> list,
|
||||
DataTypeComponent comp) {
|
||||
if (list.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
DataType dataType = comp.getDataType();
|
||||
String name = comp.getFieldName();
|
||||
Iterator<DataTypeComponent> it = list.iterator();
|
||||
while (it.hasNext()) {
|
||||
DataTypeComponent next = it.next();
|
||||
if (next.getDataType().isEquivalent(dataType) &&
|
||||
Objects.equals(name, next.getFieldName())) {
|
||||
it.remove();
|
||||
return next;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<DataTypeComponent> getZeroLengthComps(DefinedComponentQueue q, int offset) {
|
||||
if (!q.hasZeroComp(offset)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<DataTypeComponent> list = new ArrayList<>();
|
||||
while (q.hasZeroComp(offset)) {
|
||||
list.add(q.next());
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private DataTypeComponent getUndefinedComp(Structure struct, int offset) {
|
||||
DataTypeComponent comp = struct.getComponentAt(offset);
|
||||
if (comp != null && comp.getDataType() == DataType.DEFAULT) {
|
||||
return comp;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private int getMaxLength(DataTypeComponent comp1, DataTypeComponent comp2,
|
||||
DataTypeComponent comp3) {
|
||||
int length = 0;
|
||||
if (comp1 != null) {
|
||||
length = comp1.getLength();
|
||||
}
|
||||
if (comp2 != null) {
|
||||
length = Math.max(length, comp2.getLength());
|
||||
}
|
||||
if (comp3 != null) {
|
||||
length = Math.max(length, comp3.getLength());
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
private void fillGaps(int nextOffset) {
|
||||
int offset = 0;
|
||||
int length = nextOffset;
|
||||
if (lastComponentLine != null) {
|
||||
offset = lastComponentLine.getOffset() + lastComponentLine.getSize();
|
||||
length = nextOffset - offset;
|
||||
}
|
||||
if (length > 0) {
|
||||
DataTypeComponent comp1 = leftStruct.getComponentAt(offset);
|
||||
DataTypeComponent comp2 = rightStruct.getComponentAt(offset);
|
||||
DataTypeComponent comp3 = mergedStruct.getComponentAt(offset);
|
||||
lines.add(
|
||||
new StructureComponentLine(CoordinatedStructureModel.this, leftStruct,
|
||||
rightStruct,
|
||||
mergedStruct, comp1, comp2, comp3, offset, length, lines.size()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class DefinedComponentQueue {
|
||||
private DataTypeComponent[] definedComponents;
|
||||
private int current = 0;
|
||||
|
||||
DefinedComponentQueue(Structure struct) {
|
||||
definedComponents = struct.getDefinedComponents();
|
||||
}
|
||||
|
||||
public boolean hasBitField(int offset) {
|
||||
if (hasNext()) {
|
||||
DataTypeComponent comp = definedComponents[current];
|
||||
if (comp.getOffset() == offset && comp.getDataType() instanceof BitFieldDataType) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean hasZeroComp(int offset) {
|
||||
if (hasNext()) {
|
||||
DataTypeComponent comp = definedComponents[current];
|
||||
return comp.getOffset() == offset && comp.getLength() == 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public DataTypeComponent next() {
|
||||
if (current >= definedComponents.length) {
|
||||
return null;
|
||||
}
|
||||
DataTypeComponent comp = definedComponents[current];
|
||||
current++;
|
||||
return comp;
|
||||
}
|
||||
|
||||
public DataTypeComponent peek() {
|
||||
if (current >= definedComponents.length) {
|
||||
return null;
|
||||
}
|
||||
return definedComponents[current];
|
||||
}
|
||||
|
||||
public int nextOffset() {
|
||||
if (hasNext()) {
|
||||
return definedComponents[current].getOffset();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public boolean hasNext() {
|
||||
return current < definedComponents.length;
|
||||
}
|
||||
}
|
||||
|
||||
public List<CoordinatedStructureLine> getLines() {
|
||||
return compareLines;
|
||||
}
|
||||
|
||||
public void addChangeListener(Callback callback) {
|
||||
changeCallbacks.add(callback);
|
||||
}
|
||||
|
||||
public List<ComparisonItem> getData(CompareId compareId) {
|
||||
List<ComparisonItem> list = new ArrayList<>();
|
||||
for (CoordinatedStructureLine line : compareLines) {
|
||||
ComparisonItem item = line.getComparisonItem(compareId);
|
||||
if (compareId == CompareId.MERGED && item.isBlank()) {
|
||||
// skip blank lines in merged structure display
|
||||
continue;
|
||||
}
|
||||
list.add(item);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public CoordinatedStructureLine getLine(int line) {
|
||||
if (line < compareLines.size()) {
|
||||
return compareLines.get(line);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void error(String message) {
|
||||
errorHandler.accept(message);
|
||||
}
|
||||
|
||||
public Structure getMergedStructure() {
|
||||
return mergedStruct;
|
||||
}
|
||||
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
/* ###
|
||||
* 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.merge.structures;
|
||||
|
||||
import java.awt.event.AdjustmentEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Class for coordinating the scrolling and line selection of the three structure display.
|
||||
*/
|
||||
class DisplayCoordinator {
|
||||
private List<CoordinatedStructureDisplay> displays = new ArrayList<>();
|
||||
private boolean isChanging;
|
||||
|
||||
void registerDisplay(CoordinatedStructureDisplay display) {
|
||||
displays.add(display);
|
||||
}
|
||||
|
||||
void notifySelectionChanged(CoordinatedStructureDisplay changedDisplay,
|
||||
int selectedIndex, ComparisonItem item) {
|
||||
if (isChanging) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
isChanging = true;
|
||||
for (CoordinatedStructureDisplay display : displays) {
|
||||
display.setSelectedItem(changedDisplay, selectedIndex, item);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
isChanging = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void notifyHorizontalScrollChanged(CoordinatedStructureDisplay d, AdjustmentEvent e) {
|
||||
if (isChanging) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
isChanging = true;
|
||||
for (CoordinatedStructureDisplay display : displays) {
|
||||
if (display != d) {
|
||||
display.setHorizontalScroll(d, e.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
isChanging = false;
|
||||
}
|
||||
}
|
||||
|
||||
void notifyVerticalScrollChanged(CoordinatedStructureDisplay d, AdjustmentEvent e) {
|
||||
if (isChanging) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
isChanging = true;
|
||||
for (CoordinatedStructureDisplay display : displays) {
|
||||
if (display != d) {
|
||||
display.setVerticalScroll(d, e.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
isChanging = false;
|
||||
}
|
||||
}
|
||||
|
||||
void setChanging(boolean b) {
|
||||
isChanging = b;
|
||||
}
|
||||
|
||||
}
|
||||
+78
@@ -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.app.merge.structures;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.AbstractListModel;
|
||||
import javax.swing.ListModel;
|
||||
|
||||
import ghidra.app.merge.structures.CoordinatedStructureLine.CompareId;
|
||||
|
||||
/**
|
||||
* The {@link ListModel} model for one of the three structure displays.
|
||||
*/
|
||||
class StructDisplayModel extends AbstractListModel<ComparisonItem> {
|
||||
|
||||
private CoordinatedStructureModel model;
|
||||
private CompareId compareId;
|
||||
private List<ComparisonItem> data;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param model the comparison model that has the coordinated lines for all three structures.
|
||||
* @param compareId the id that says this is either the left, right, or merged list model.
|
||||
* Used to get the appropriate list of comparison items from the
|
||||
* {@link CoordinatedStructureModel}
|
||||
*/
|
||||
StructDisplayModel(CoordinatedStructureModel model, CompareId compareId) {
|
||||
this.model = model;
|
||||
this.compareId = compareId;
|
||||
model.addChangeListener(() -> modelChanged());
|
||||
data = model.getData(compareId);
|
||||
}
|
||||
|
||||
private void modelChanged() {
|
||||
data = model.getData(compareId);
|
||||
fireContentsChanged(this, 0, model.getSize());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize() {
|
||||
return data.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComparisonItem getElementAt(int index) {
|
||||
return data.get(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list index of the corresponding item in this list model. Uses the line number info
|
||||
* from the given item to find its internal item that has the same line number.
|
||||
* @param item the item to use to find the corresponding item in this model
|
||||
* @return the list index in this model for the item that has the same line number as the given
|
||||
* item or -1 if not such item exits.
|
||||
*/
|
||||
int getIndex(ComparisonItem item) {
|
||||
if (item == null) {
|
||||
return -1;
|
||||
}
|
||||
return Collections.binarySearch(data, item);
|
||||
}
|
||||
|
||||
}
|
||||
+444
@@ -0,0 +1,444 @@
|
||||
/* ###
|
||||
* 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.merge.structures;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import ghidra.program.model.data.*;
|
||||
|
||||
/**
|
||||
* {@link CoordinatedStructureLine} for showing structure components.
|
||||
*/
|
||||
public class StructureComponentLine extends CoordinatedStructureLine {
|
||||
public static final int COMPONENT_INDENT = 5;
|
||||
public static final int OFFSET_SIZE = 10;
|
||||
public static final int MIN_DT_SIZE = 100;
|
||||
public static final int MIN_NAME_SIZE = 10;
|
||||
public static final int MIN_COMMENT_SIZE = 10;
|
||||
private DataTypeComponent mergedComp;
|
||||
private int offset;
|
||||
private int length;
|
||||
private Structure mergedStruct;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param model the {@link CoordinatedStructureModel}
|
||||
* @param leftStruct the left structure
|
||||
* @param rightStruct the right structure
|
||||
* @param mergedStruct the merged structure
|
||||
* @param left the left component at the offset (can be null)
|
||||
* @param right the right component at the offset (can be null)
|
||||
* @param merged the merged component at the offset (can be null)
|
||||
* @param offset the offset into the structures for this component line
|
||||
* @param length the size of the this component line (will be the largest of the three)
|
||||
* @param line the line number where this component will be shown in the overall list of
|
||||
* line items (including name, description, info, etc.)
|
||||
*/
|
||||
StructureComponentLine(CoordinatedStructureModel model, Structure leftStruct,
|
||||
Structure rightStruct,
|
||||
Structure mergedStruct, DataTypeComponent left, DataTypeComponent right,
|
||||
DataTypeComponent merged, int offset, int length, int line) {
|
||||
super(model);
|
||||
this.mergedStruct = mergedStruct;
|
||||
this.left = new StructureComponentItem(left, right, leftStruct, line);
|
||||
this.right = new StructureComponentItem(right, left, rightStruct, line);
|
||||
this.merged = new StructureComponentItem(merged, null, mergedStruct, line);
|
||||
this.mergedComp = merged;
|
||||
this.offset = offset;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
int getSize() {
|
||||
return length;
|
||||
}
|
||||
|
||||
int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
StructureComponentLine other = (StructureComponentLine) obj;
|
||||
return Objects.equals(left, other.left) && length == other.length &&
|
||||
Objects.equals(merged, other.merged) && offset == other.offset &&
|
||||
Objects.equals(right, other.right);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append("\nLeft: ");
|
||||
buf.append(left.toString());
|
||||
buf.append("\n");
|
||||
buf.append("Right: ");
|
||||
buf.append(right.toString());
|
||||
buf.append("\n");
|
||||
buf.append("Merged: ");
|
||||
buf.append(merged.toString());
|
||||
buf.append("\n");
|
||||
buf.append("offset = ");
|
||||
buf.append(offset);
|
||||
buf.append(", length = ");
|
||||
buf.append(length);
|
||||
buf.append("\n");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Class for the individual {@link ComparisonItem}s for each of the structures.
|
||||
*/
|
||||
private class StructureComponentItem extends ComparisonItem {
|
||||
private static final int OFFSET_COL = 1;
|
||||
private static final int DATATYPE_COL = 2;
|
||||
private static final int NAME_COL = 3;
|
||||
private static final int COMMENT_COL = 4;
|
||||
|
||||
private DataTypeComponent myComp;
|
||||
private DataTypeComponent otherComp;
|
||||
private Structure struct;
|
||||
|
||||
StructureComponentItem(DataTypeComponent comp, DataTypeComponent other, Structure struct,
|
||||
int line) {
|
||||
super("Component", line);
|
||||
this.myComp = comp;
|
||||
this.otherComp = other;
|
||||
this.struct = struct;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnText(int column) {
|
||||
if (myComp == null) {
|
||||
return "";
|
||||
}
|
||||
switch (column) {
|
||||
case OFFSET_COL:
|
||||
return Integer.toString(offset) + " ";
|
||||
case DATATYPE_COL:
|
||||
DataType dataType = myComp.getDataType();
|
||||
if (dataType == DataType.DEFAULT) {
|
||||
return "undefined (" + getUndefinedLength() + ")";
|
||||
}
|
||||
return getDataTypeDisplayName(dataType);
|
||||
case NAME_COL:
|
||||
String name = myComp.getFieldName();
|
||||
return name == null ? "" : name;
|
||||
case COMMENT_COL:
|
||||
String comment = myComp.getComment();
|
||||
return StringUtils.isBlank(comment) ? "" : "// " + comment;
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private String getDataTypeDisplayName(DataType dataType) {
|
||||
String name = dataType.getDisplayName();
|
||||
if (dataType instanceof BitFieldDataType bfdt) {
|
||||
int startBit = bfdt.getBitOffset();
|
||||
int endBit = startBit + bfdt.getBitSize() - 1;
|
||||
name += " (%d, %d)".formatted(startBit, endBit);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
private int getUndefinedLength() {
|
||||
// loop until we find the next line that has a different offset than this line
|
||||
// The difference will be the undefined length;
|
||||
for (int i = getLine() + 1; i < model.getSize(); i++) {
|
||||
CoordinatedStructureLine compareLine = model.getLine(i);
|
||||
if (!(compareLine instanceof StructureComponentLine componentLine)) {
|
||||
break;
|
||||
}
|
||||
if (componentLine.offset != offset) {
|
||||
return componentLine.offset - offset;
|
||||
}
|
||||
}
|
||||
// otherwise, the length is to the end of the struct
|
||||
return struct.getLength() - offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLeftJustified(int column) {
|
||||
return column != 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinWidth(int column) {
|
||||
switch (column) {
|
||||
case 0:
|
||||
return COMPONENT_INDENT;
|
||||
case OFFSET_COL:
|
||||
return OFFSET_SIZE;
|
||||
case DATATYPE_COL:
|
||||
return MIN_DT_SIZE;
|
||||
case NAME_COL:
|
||||
return MIN_NAME_SIZE;
|
||||
case COMMENT_COL:
|
||||
return MIN_COMMENT_SIZE;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canApplyAny() {
|
||||
if (myComp == mergedComp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !(isDatatypeApplied() && isNameApplied() && isCommentApplied());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAppliable() {
|
||||
if (myComp == null || myComp.getDataType() == DataType.DEFAULT) {
|
||||
return false;
|
||||
}
|
||||
if (otherComp == null) {
|
||||
return true;
|
||||
}
|
||||
return !myComp.isEquivalent(otherComp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAppliable(int column) {
|
||||
if (myComp == mergedComp) {
|
||||
return false;
|
||||
}
|
||||
if (myComp == null || myComp == mergedComp ||
|
||||
myComp.getDataType() == DataType.DEFAULT) {
|
||||
return false;
|
||||
}
|
||||
return column == DATATYPE_COL || column == NAME_COL || column == COMMENT_COL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplied(int column) {
|
||||
if (myComp == null || mergedComp == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (column) {
|
||||
case DATATYPE_COL:
|
||||
return isDatatypeApplied();
|
||||
case NAME_COL:
|
||||
return isNameApplied();
|
||||
case COMMENT_COL:
|
||||
return isNameApplied();
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canClear() {
|
||||
return mergedComp != null && mergedComp.getDataType() != DataType.DEFAULT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return myComp == null ? 0 : myComp.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
StructureComponentItem other = (StructureComponentItem) obj;
|
||||
if (myComp == null) {
|
||||
return other.myComp == null;
|
||||
}
|
||||
if (myComp.getOffset() != other.myComp.getOffset()) {
|
||||
return false;
|
||||
}
|
||||
if (!myComp.getDataType().isEquivalent(other.myComp.getDataType())) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(myComp.getFieldName(), other.myComp.getFieldName())) {
|
||||
return false;
|
||||
}
|
||||
return Objects.equals(myComp.getComment(), other.myComp.getComment());
|
||||
}
|
||||
|
||||
private boolean isDatatypeApplied() {
|
||||
if (myComp == null || mergedComp == null) {
|
||||
return false;
|
||||
}
|
||||
return myComp.getDataType().isEquivalent(mergedComp.getDataType());
|
||||
}
|
||||
|
||||
private boolean isNameApplied() {
|
||||
if (myComp == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mergedComp == null) {
|
||||
return false;
|
||||
}
|
||||
return Objects.equals(myComp.getFieldName(), mergedComp.getFieldName());
|
||||
}
|
||||
|
||||
private boolean isCommentApplied() {
|
||||
return Objects.equals(myComp.getComment(), mergedComp.getComment());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (myComp == null) {
|
||||
return "";
|
||||
}
|
||||
StringBuffer buffer = new StringBuffer();
|
||||
buffer.append(myComp.getOffset());
|
||||
buffer.append(" ");
|
||||
buffer.append(getColumnText(DATATYPE_COL));
|
||||
|
||||
String name = myComp.getFieldName();
|
||||
if (name != null) {
|
||||
buffer.append(" " + name);
|
||||
}
|
||||
String comment = myComp.getComment();
|
||||
if (!StringUtils.isBlank(comment)) {
|
||||
buffer.append(" // ");
|
||||
buffer.append(comment);
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyAll() {
|
||||
if (myComp.getLength() == 0) {
|
||||
mergedStruct.insertAtOffset(offset, myComp.getDataType(), 0, myComp.getFieldName(),
|
||||
myComp.getComment());
|
||||
}
|
||||
else if (myComp.getDataType() instanceof BitFieldDataType bfdt) {
|
||||
makeRoomForBitField(bfdt);
|
||||
DataType dt = bfdt.getBaseDataType();
|
||||
int byteSize = bfdt.getStorageSize();
|
||||
int bitOffset = bfdt.getBitOffset();
|
||||
int bitLength = bfdt.getBitSize();
|
||||
String name = myComp.getFieldName();
|
||||
String comment = myComp.getComment();
|
||||
try {
|
||||
mergedStruct.insertBitFieldAt(offset, byteSize, bitOffset, dt, bitLength, name,
|
||||
comment);
|
||||
}
|
||||
catch (InvalidDataTypeException e) {
|
||||
model.error("Error applying bitfield component at offset " + offset + ": " +
|
||||
e.getMessage());
|
||||
}
|
||||
}
|
||||
else {
|
||||
makeRoom();
|
||||
mergedStruct.replaceAtOffset(offset, myComp.getDataType(), myComp.getLength(),
|
||||
myComp.getFieldName(), myComp.getComment());
|
||||
}
|
||||
modelChanged();
|
||||
}
|
||||
|
||||
private void makeRoomForBitField(BitFieldDataType bfdt) {
|
||||
|
||||
List<DataTypeComponent> comps = mergedStruct.getComponentsContaining(offset);
|
||||
for (DataTypeComponent comp : comps) {
|
||||
DataType dt = comp.getDataType();
|
||||
if (dt instanceof BitFieldDataType otherBfdt) {
|
||||
if (BitFieldDataType.intersects(bfdt, otherBfdt, offset, comp.getOffset())) {
|
||||
mergedStruct.clearComponent(comp.getOrdinal());
|
||||
}
|
||||
}
|
||||
else if (dt != DataType.DEFAULT && comp.getOffset() == offset &&
|
||||
comp.getLength() != 0) {
|
||||
mergedStruct.clearComponent(comp.getOrdinal());
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = offset + 1; i < offset + length; i++) {
|
||||
comps = mergedStruct.getComponentsContaining(i);
|
||||
for (DataTypeComponent comp : comps) {
|
||||
// To avoid repeating looking at components we already examined, only look
|
||||
// at components that start at the offset being examined. (We already
|
||||
// handled all the one that extend into the new component, so only need
|
||||
// to worry about the ones that start inside it.)
|
||||
if (comp.getOffset() != i) {
|
||||
continue;
|
||||
}
|
||||
DataType dt = comp.getDataType();
|
||||
if (dt instanceof BitFieldDataType otherBfdt) {
|
||||
if (BitFieldDataType.intersects(bfdt, otherBfdt, offset, i)) {
|
||||
mergedStruct.clearComponent(comp.getOrdinal());
|
||||
}
|
||||
}
|
||||
else {
|
||||
// if we have anything other than a bitfield, then wipe them all out
|
||||
// because only bitfields can coexist with other bitfields.
|
||||
mergedStruct.clearAtOffset(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
mergedStruct.clearComponent(mergedComp.getOrdinal());
|
||||
modelChanged();
|
||||
}
|
||||
|
||||
private void makeRoom() {
|
||||
if (myComp.getLength() == 0) {
|
||||
// just make sure there is that starts here
|
||||
if (mergedStruct.getComponentAt(offset) == null) {
|
||||
mergedStruct.clearAtOffset(offset);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
List<DataTypeComponent> list = mergedStruct.getComponentsContaining(offset);
|
||||
for (DataTypeComponent comp : list) {
|
||||
DataType dt = comp.getDataType();
|
||||
if (dt != DataType.DEFAULT && comp.getOffset() == offset && comp.getLength() != 0) {
|
||||
mergedStruct.clearComponent(comp.getOrdinal());
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = offset + 1; i < offset + length; i++) {
|
||||
mergedStruct.clearAtOffset(i);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBlank() {
|
||||
return myComp == null;
|
||||
}
|
||||
}
|
||||
}
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
/* ###
|
||||
* 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.merge.structures;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import ghidra.program.model.data.Structure;
|
||||
|
||||
/**
|
||||
* {@link CoordinatedStructureLine} for showing structure description (its comment).
|
||||
*/
|
||||
public class StructureDescriptionLine extends CoordinatedStructureLine {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param model the {@link CoordinatedStructureModel}
|
||||
* @param leftStruct the left structure
|
||||
* @param rightStruct the right structure
|
||||
* @param mergedStruct the merged structure
|
||||
* @param line the line number where this component will be shown in the overall list of
|
||||
* line items (including name, description, info, etc.)
|
||||
*/
|
||||
public StructureDescriptionLine(CoordinatedStructureModel model, Structure leftStruct,
|
||||
Structure rightStruct, Structure mergedStruct, int line) {
|
||||
super(model);
|
||||
this.left = new DescriptionItem(leftStruct, mergedStruct, line);
|
||||
this.right = new DescriptionItem(rightStruct, mergedStruct, line);
|
||||
this.merged = new DescriptionItem(mergedStruct, mergedStruct, line);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append("Left name: ");
|
||||
buf.append(((DescriptionItem) left).struct.getName());
|
||||
buf.append(", Right name: ");
|
||||
buf.append(((DescriptionItem) right).struct.getName());
|
||||
buf.append(", Merged name: ");
|
||||
buf.append(((DescriptionItem) merged).struct.getName());
|
||||
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Class for the individual {@link ComparisonItem}s for each of the structures.
|
||||
*/
|
||||
private class DescriptionItem extends ComparisonItem {
|
||||
private static final int STRUCT_COMMENT_COL = 0;
|
||||
private Structure struct;
|
||||
private Structure mergedStruct;
|
||||
|
||||
DescriptionItem(Structure struct, Structure mergedStruct, int line) {
|
||||
super("Structure Comment", line);
|
||||
this.struct = struct;
|
||||
this.mergedStruct = mergedStruct;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnText(int column) {
|
||||
if (column != STRUCT_COMMENT_COL) {
|
||||
return "";
|
||||
}
|
||||
String description = struct.getDescription();
|
||||
|
||||
if (!StringUtils.isBlank(description)) {
|
||||
description = "// " + description;
|
||||
}
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canApplyAny() {
|
||||
return !isApplied(STRUCT_COMMENT_COL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAppliable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAppliable(int column) {
|
||||
if (struct == mergedStruct) {
|
||||
return false; // cant apply the results to itself
|
||||
}
|
||||
return column == STRUCT_COMMENT_COL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplied(int column) {
|
||||
if (column == STRUCT_COMMENT_COL) {
|
||||
return Objects.equals(struct.getDescription(), mergedStruct.getDescription());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinWidth(int column) {
|
||||
return column == STRUCT_COMMENT_COL ? 200 : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return struct.getName().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String description = struct.getDescription();
|
||||
if (StringUtils.isBlank(description)) {
|
||||
return "";
|
||||
}
|
||||
return "// " + description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
DescriptionItem other = (DescriptionItem) obj;
|
||||
return Objects.equals(struct.getDescription(), other.struct.getDescription());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyAll() {
|
||||
String description = struct.getDescription();
|
||||
mergedStruct.setDescription(description);
|
||||
modelChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBlank() {
|
||||
return StringUtils.isBlank(struct.getDescription());
|
||||
}
|
||||
}
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
/* ###
|
||||
* 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.merge.structures;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* {@link CoordinatedStructureLine} for showing invariant structure information. This includes
|
||||
* syntax ("{" and "}") and structure details (size, alignment, packing).
|
||||
*/
|
||||
public class StructureInfoLine extends CoordinatedStructureLine {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param model the {@link CoordinatedStructureModel}
|
||||
* @param left the string to be displayed in the left display
|
||||
* @param right the string to be displayed in the right display
|
||||
* @param merged the string to be displayed in the merged display
|
||||
* @param line the line number of this line in the overall display
|
||||
* @param type the type of info ("Syntax", or "Structure details")
|
||||
*/
|
||||
public StructureInfoLine(CoordinatedStructureModel model, String left, String right,
|
||||
String merged, int line, String type) {
|
||||
super(model);
|
||||
this.left = new InfoItem(left, type, line);
|
||||
this.right = new InfoItem(right, type, line);
|
||||
this.merged = new InfoItem(merged, type, line);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param model the {@link CoordinatedStructureModel}
|
||||
* @param all the string to be displayed in all displays
|
||||
* @param line the line number of this line in the overall display
|
||||
* @param type the type of info ("Syntax", or "Structure details")
|
||||
*/
|
||||
public StructureInfoLine(CoordinatedStructureModel model, String all, int line, String type) {
|
||||
this(model, all, all, all, line, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append("left: ");
|
||||
buf.append(((InfoItem) left).info);
|
||||
buf.append(", right: ");
|
||||
buf.append(((InfoItem) right).info);
|
||||
buf.append(", merged: ");
|
||||
buf.append(((InfoItem) merged).info);
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Class for the individual {@link ComparisonItem}s for each of the structures.
|
||||
*/
|
||||
private class InfoItem extends ComparisonItem {
|
||||
|
||||
private String info;
|
||||
|
||||
InfoItem(String info, String type, int line) {
|
||||
super(type, line);
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnText(int column) {
|
||||
if (column == 0) {
|
||||
return info;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinWidth(int column) {
|
||||
return column == 0 ? 350 : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return info.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
InfoItem other = (InfoItem) obj;
|
||||
return Objects.equals(info, other.info);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return info;
|
||||
}
|
||||
}
|
||||
}
|
||||
+420
@@ -0,0 +1,420 @@
|
||||
/* ###
|
||||
* 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.merge.structures;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.MouseEvent;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
|
||||
import docking.*;
|
||||
import docking.action.DockingAction;
|
||||
import docking.action.builder.ActionBuilder;
|
||||
import docking.widgets.button.GRadioButton;
|
||||
import ghidra.app.merge.structures.CoordinatedStructureLine.CompareId;
|
||||
import ghidra.program.model.data.Structure;
|
||||
import ghidra.util.HelpLocation;
|
||||
import ghidra.util.MessageType;
|
||||
import utility.function.ExceptionalConsumer;
|
||||
|
||||
/**
|
||||
* Dialog for merging structures. The dialog is constructed given two structures and it will
|
||||
* merge them, producing a third merged structure. The dialog will then display all three
|
||||
* structures and provide controls for dealing with conflicts, allowing the user to choose
|
||||
* components from the left or right side structures.
|
||||
* <P>
|
||||
* The dialog itself doesn't do anything with the resulting merged structure. Clients need
|
||||
* to provide an apply consumer that will be called when the user presses the dialog's apply
|
||||
* button.
|
||||
* <P>
|
||||
* The dialog also provides the following actions as keyboard only actions:
|
||||
* <OL>
|
||||
* <LI>Apply Item (<SPACE>): pressing the space bar key will apply the currently focussed and
|
||||
* selected item from either the left side or right sided. (Assuming it is appliable).</LI>
|
||||
* <LI>Focus Left Side (<LEFT ARROW>): pressing the left arrow will give focus to the left side
|
||||
* display.</LI>
|
||||
* <LI>Focus Right Side (<RIGHT ARROW>): pressing the right arrow will give focus to the right side
|
||||
* display.</LI>
|
||||
* </OL>
|
||||
*/
|
||||
public class StructureMergeDialog extends DialogComponentProvider {
|
||||
private CoordinatedStructureModel model;
|
||||
private CoordinatedStructureDisplay mergedDisplay;
|
||||
private CoordinatedStructureDisplay leftDisplay;
|
||||
private CoordinatedStructureDisplay rightDisplay;
|
||||
private LeftRightButtonPanel leftRightChooserPanel;
|
||||
private DisplayCoordinator coordinator;
|
||||
private ExceptionalConsumer<Structure, Exception> applyConsumer;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param title the dialog title.
|
||||
* @param struct1 the first structure (will receive precedence for any conflicting components
|
||||
* @param struct2 the second structure
|
||||
* @param applyConsumer the consumer to call when the user presses the apply button. This
|
||||
* consumer can throw an exception which will be displayed in the dialog and the dialog won't
|
||||
* close. If the apply does not throw an exception, the dialog will be closed.
|
||||
*/
|
||||
public StructureMergeDialog(String title, Structure struct1, Structure struct2,
|
||||
ExceptionalConsumer<Structure, Exception> applyConsumer) {
|
||||
super(title);
|
||||
this.applyConsumer = applyConsumer;
|
||||
setHelpLocation(new HelpLocation("DataTypeManagerPlugin", "MergeStructures"));
|
||||
|
||||
model = new CoordinatedStructureModel(struct1, struct2, msg -> handleError(msg));
|
||||
|
||||
buildDisplays();
|
||||
addWorkPanel(buildMainPanel());
|
||||
addActions();
|
||||
|
||||
addApplyButton();
|
||||
addCancelButton();
|
||||
rootPanel.setFocusCycleRoot(true);
|
||||
rootPanel.setFocusTraversalPolicy(new StructureMergeDialogFocusTraveralPolicy());
|
||||
}
|
||||
|
||||
private void addActions() {
|
||||
DockingAction applyAction = new ActionBuilder("Apply", getClass().getSimpleName())
|
||||
.keyBinding("SPACE")
|
||||
.description("Applies the selected structure component to the merged structure.")
|
||||
.withContext(StructureMergeDialogContext.class)
|
||||
.onAction(c -> toggleApply(c.getComparisonItem()))
|
||||
.build();
|
||||
addAction(applyAction);
|
||||
|
||||
DockingAction goToLeftAction =
|
||||
new ActionBuilder("Left Display Action", getClass().getSimpleName())
|
||||
.keyBinding("LEFT")
|
||||
.description(
|
||||
"Give keyboard focus to left side view in structure merger dialog.")
|
||||
.onAction(c -> leftDisplay.getList().requestFocus())
|
||||
.build();
|
||||
addAction(goToLeftAction);
|
||||
DockingAction goToRightAction =
|
||||
new ActionBuilder("Right Display Action", getClass().getSimpleName())
|
||||
.keyBinding("RIGHT")
|
||||
.description(
|
||||
"Give keyboard focus to right side view in structure merger dialog.")
|
||||
.onAction(c -> rightDisplay.getList().requestFocus())
|
||||
.build();
|
||||
addAction(goToRightAction);
|
||||
|
||||
}
|
||||
|
||||
private void toggleApply(ComparisonItem item) {
|
||||
if (item == null) {
|
||||
return;
|
||||
}
|
||||
if (item.canApplyAny()) {
|
||||
item.applyAll();
|
||||
}
|
||||
else if (item.canClear()) {
|
||||
item.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActionContext getActionContext(MouseEvent event) {
|
||||
Component c = getComponent();
|
||||
KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager();
|
||||
Component focusedComponent = kfm.getFocusOwner();
|
||||
if (focusedComponent != null && SwingUtilities.isDescendingFrom(focusedComponent, c)) {
|
||||
c = focusedComponent;
|
||||
}
|
||||
|
||||
if (event != null) {
|
||||
Component sourceComponent = event.getComponent();
|
||||
if (sourceComponent != null) {
|
||||
c = sourceComponent;
|
||||
}
|
||||
}
|
||||
|
||||
CoordinatedStructureDisplay display = findDisplayForComponent(c);
|
||||
return new StructureMergeDialogContext(this, c, display);
|
||||
}
|
||||
|
||||
private CoordinatedStructureDisplay findDisplayForComponent(Component c) {
|
||||
|
||||
if (SwingUtilities.isDescendingFrom(c, leftDisplay)) {
|
||||
return leftDisplay;
|
||||
}
|
||||
if (SwingUtilities.isDescendingFrom(c, rightDisplay)) {
|
||||
return rightDisplay;
|
||||
}
|
||||
if (SwingUtilities.isDescendingFrom(c, leftDisplay)) {
|
||||
return mergedDisplay;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyCallback() {
|
||||
try {
|
||||
applyConsumer.accept(model.getMergedStructure());
|
||||
close();
|
||||
}
|
||||
catch (Exception e) {
|
||||
setStatusText("Apply Failed: " + e.getMessage(), MessageType.ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleError(String errorMsg) {
|
||||
setStatusText(errorMsg);
|
||||
}
|
||||
|
||||
private void buildDisplays() {
|
||||
coordinator = new DisplayCoordinator();
|
||||
leftDisplay = new CoordinatedStructureDisplay("Struct 1",
|
||||
new StructDisplayModel(model, CompareId.LEFT), coordinator);
|
||||
rightDisplay = new CoordinatedStructureDisplay("Struct 2",
|
||||
new StructDisplayModel(model, CompareId.RIGHT), coordinator);
|
||||
mergedDisplay = new CoordinatedStructureDisplay("Merged",
|
||||
new StructDisplayModel(model, CompareId.MERGED), coordinator);
|
||||
leftRightChooserPanel = new LeftRightButtonPanel();
|
||||
int rowHeight = Math.max(leftDisplay.getRowHeight(), leftRightChooserPanel.getRowHeight());
|
||||
leftRightChooserPanel.setRowHeight(rowHeight);
|
||||
leftDisplay.setRowHeight(rowHeight);
|
||||
rightDisplay.setRowHeight(rowHeight);
|
||||
mergedDisplay.setRowHeight(rowHeight);
|
||||
}
|
||||
|
||||
private JComponent buildMainPanel() {
|
||||
JPanel panel = new JPanel(new GridLayout(2, 1, 10, 10));
|
||||
panel.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
|
||||
panel.add(buildSourcePanel());
|
||||
panel.add(mergedDisplay);
|
||||
return panel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Customized focus traversal policy to avoid traversing to any of the apply buttons. The
|
||||
* focus will go as follows: left display, right display, merged display, apply button, and
|
||||
* finally cancel button.
|
||||
*/
|
||||
private class StructureMergeDialogFocusTraveralPolicy extends FocusTraversalPolicy {
|
||||
|
||||
@Override
|
||||
public Component getComponentAfter(Container aContainer, Component aComponent) {
|
||||
if (SwingUtilities.isDescendingFrom(aComponent, leftDisplay)) {
|
||||
return rightDisplay.getList();
|
||||
}
|
||||
if (SwingUtilities.isDescendingFrom(aComponent, rightDisplay)) {
|
||||
return mergedDisplay.getList();
|
||||
}
|
||||
if (SwingUtilities.isDescendingFrom(aComponent, mergedDisplay)) {
|
||||
return applyButton;
|
||||
}
|
||||
if (aComponent == applyButton) {
|
||||
return cancelButton;
|
||||
}
|
||||
return leftDisplay.getList();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getComponentBefore(Container aContainer, Component aComponent) {
|
||||
if (aComponent == cancelButton) {
|
||||
return applyButton;
|
||||
}
|
||||
if (aComponent == applyButton) {
|
||||
return mergedDisplay.getList();
|
||||
}
|
||||
if (SwingUtilities.isDescendingFrom(aComponent, mergedDisplay)) {
|
||||
return rightDisplay.getList();
|
||||
}
|
||||
if (SwingUtilities.isDescendingFrom(aComponent, rightDisplay)) {
|
||||
return leftDisplay.getList();
|
||||
}
|
||||
return cancelButton;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getFirstComponent(Container aContainer) {
|
||||
return leftDisplay.getList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getLastComponent(Container aContainer) {
|
||||
return cancelButton;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getDefaultComponent(Container aContainer) {
|
||||
return leftDisplay.getList();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private Component buildSourcePanel() {
|
||||
// Using grid bag layout so that the two structure displays on either side of the
|
||||
// button panel get all the available extra space. The middle button panel is always
|
||||
// fixed width.
|
||||
|
||||
JPanel panel = new JPanel(new GridBagLayout());
|
||||
GridBagConstraints gbc = new GridBagConstraints();
|
||||
gbc.gridx = 0;
|
||||
gbc.gridy = 0;
|
||||
gbc.gridwidth = 1;
|
||||
gbc.gridheight = 1;
|
||||
gbc.fill = GridBagConstraints.BOTH;
|
||||
gbc.weightx = 0.5;
|
||||
gbc.weighty = 1;
|
||||
panel.add(leftDisplay, gbc);
|
||||
|
||||
gbc.gridx = 2;
|
||||
panel.add(rightDisplay, gbc);
|
||||
|
||||
gbc.gridx = 1;
|
||||
gbc.weightx = 0.0;
|
||||
panel.add(leftRightChooserPanel, gbc);
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
private class LeftRightButtonPanel extends JPanel {
|
||||
private static int BUTTON_GAP = 7;
|
||||
private int rowHeight;
|
||||
private int buttonWidth;
|
||||
private int buttonHeight;
|
||||
private JPanel radioButtonPanel;
|
||||
private JViewport buttonPanelViewport;
|
||||
|
||||
LeftRightButtonPanel() {
|
||||
super(new BorderLayout());
|
||||
Insets insets = leftDisplay.getInsets();
|
||||
setBorder(BorderFactory.createEmptyBorder(insets.top, 1, insets.bottom, 1));
|
||||
GRadioButton button = new GRadioButton();
|
||||
Dimension preferredButtonSize = button.getPreferredSize();
|
||||
rowHeight = preferredButtonSize.height;
|
||||
buttonHeight = preferredButtonSize.height;
|
||||
buttonWidth = preferredButtonSize.width;
|
||||
radioButtonPanel = new JPanel(null);
|
||||
|
||||
// we need to set the preferred size of our inner panel to be at least as big as the
|
||||
// the corresponding coordinatedStructureViews or the scrolling doesn't work correctly.
|
||||
// if it is smaller, it limits the scroll position to its preferred size.
|
||||
radioButtonPanel.setPreferredSize(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE));
|
||||
buttonPanelViewport = new JViewport();
|
||||
buttonPanelViewport.setView(radioButtonPanel);
|
||||
add(buttonPanelViewport, BorderLayout.CENTER);
|
||||
leftDisplay.addViewportListener(e -> viewportChanged(e));
|
||||
model.addChangeListener(() -> buildButtons());
|
||||
}
|
||||
|
||||
private void viewportChanged(ChangeEvent e) {
|
||||
JViewport viewport = (JViewport) e.getSource();
|
||||
buttonPanelViewport.setViewSize(viewport.getViewSize());
|
||||
buttonPanelViewport.setViewPosition(new Point(0, viewport.getViewPosition().y));
|
||||
buildButtons();
|
||||
}
|
||||
|
||||
int getRowHeight() {
|
||||
return rowHeight;
|
||||
}
|
||||
|
||||
void setRowHeight(int rowHeight) {
|
||||
this.rowHeight = rowHeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getPreferredSize() {
|
||||
Insets insets = getInsets();
|
||||
return new Dimension(2 * buttonWidth + BUTTON_GAP + insets.left + insets.right, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getMinimumSize() {
|
||||
return getPreferredSize();
|
||||
}
|
||||
|
||||
private void buildButtons() {
|
||||
int index1 = leftDisplay.getFirstVisibleIndex();
|
||||
int index2 = leftDisplay.getLastVisibleIndex();
|
||||
buildButtons(index1, index2);
|
||||
repaint();
|
||||
}
|
||||
|
||||
public void buildButtons(int firstIndex, int lastIndex) {
|
||||
if (firstIndex < 0) {
|
||||
return;
|
||||
}
|
||||
radioButtonPanel.removeAll();
|
||||
|
||||
for (int i = firstIndex; i <= lastIndex; i++) {
|
||||
int y = i * rowHeight + CoordinatedStructureDisplay.MARGIN;
|
||||
ComparisonItem leftItem = leftDisplay.getItem(i);
|
||||
ComparisonItem rightItem = rightDisplay.getItem(i);
|
||||
if (leftItem.isAppliable()) {
|
||||
buildButton(0, y, leftItem, true);
|
||||
}
|
||||
if (rightItem.isAppliable()) {
|
||||
buildButton(buttonWidth + BUTTON_GAP, y, rightItem, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void buildButton(int x, int y, ComparisonItem item, boolean isLeft) {
|
||||
GRadioButton button = new GRadioButton();
|
||||
button.setBounds(x, y, buttonWidth, buttonHeight);
|
||||
button.setSelected(!item.canApplyAny());
|
||||
button.addActionListener(e -> {
|
||||
if (!button.isSelected() && !item.canClear()) {
|
||||
button.setSelected(true);
|
||||
return;
|
||||
}
|
||||
coordinator.setChanging(true);
|
||||
try {
|
||||
if (button.isSelected()) {
|
||||
item.applyAll();
|
||||
}
|
||||
else {
|
||||
item.clear();
|
||||
}
|
||||
|
||||
// We need to validate the mergedDisplay with the coordinator disabled.
|
||||
// Otherwise, it will revalidate on the next repaint which may cause it to
|
||||
// resize, which in turn will move its viewport, which then affects the source
|
||||
// panel, causing them to jump as buttons are pressed.
|
||||
mergedDisplay.validate();
|
||||
CoordinatedStructureDisplay focusDisplay = isLeft ? leftDisplay : rightDisplay;
|
||||
focusDisplay.getList().requestFocus();
|
||||
}
|
||||
finally {
|
||||
coordinator.setChanging(false);
|
||||
}
|
||||
});
|
||||
radioButtonPanel.add(button);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class StructureMergeDialogContext extends DefaultActionContext {
|
||||
|
||||
private CoordinatedStructureDisplay display;
|
||||
|
||||
public StructureMergeDialogContext(DialogComponentProvider dialog, Component source,
|
||||
CoordinatedStructureDisplay display) {
|
||||
super(null, dialog, source);
|
||||
this.display = display;
|
||||
}
|
||||
|
||||
public ComparisonItem getComparisonItem() {
|
||||
return display != null ? display.getSelectedItem() : null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+165
@@ -0,0 +1,165 @@
|
||||
/* ###
|
||||
* 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.merge.structures;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import ghidra.program.model.data.Structure;
|
||||
import ghidra.util.InvalidNameException;
|
||||
import ghidra.util.exception.DuplicateNameException;
|
||||
|
||||
/**
|
||||
* {@link CoordinatedStructureLine} for showing the structure's name.
|
||||
*/
|
||||
public class StructureNameLine extends CoordinatedStructureLine {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param model the {@link CoordinatedStructureModel}
|
||||
* @param leftStruct the left structure
|
||||
* @param rightStruct the right structure
|
||||
* @param mergedStruct the merged structure
|
||||
* @param line the line number where this component will be shown in the overall list of
|
||||
* line items (including name, description, info, etc.)
|
||||
*/
|
||||
public StructureNameLine(CoordinatedStructureModel model, Structure leftStruct,
|
||||
Structure rightStruct, Structure mergedStruct, int line) {
|
||||
super(model);
|
||||
this.left = new NameItem(leftStruct, mergedStruct, line);
|
||||
this.right = new NameItem(rightStruct, mergedStruct, line);
|
||||
this.merged = new NameItem(mergedStruct, mergedStruct, line);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append("Left name: ");
|
||||
buf.append(((NameItem) left).struct.getName());
|
||||
buf.append(", Right name: ");
|
||||
buf.append(((NameItem) right).struct.getName());
|
||||
buf.append(", Merged name: ");
|
||||
buf.append(((NameItem) merged).struct.getName());
|
||||
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Class for the individual {@link ComparisonItem}s for each of the structures.
|
||||
*/
|
||||
private class NameItem extends ComparisonItem {
|
||||
private static final int STRUCT_KEYWORD_COL = 0;
|
||||
private static final int STRUCT_NAME_COL = 1;
|
||||
private Structure struct;
|
||||
private Structure mergedStruct;
|
||||
|
||||
NameItem(Structure struct, Structure mergedStruct, int line) {
|
||||
super("Structure Name", line);
|
||||
this.struct = struct;
|
||||
this.mergedStruct = mergedStruct;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnText(int column) {
|
||||
switch (column) {
|
||||
case STRUCT_KEYWORD_COL:
|
||||
return "Struct";
|
||||
case STRUCT_NAME_COL:
|
||||
return struct.getName();
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canApplyAny() {
|
||||
return !isApplied(STRUCT_NAME_COL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAppliable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAppliable(int column) {
|
||||
if (struct == mergedStruct) {
|
||||
return false; // cant apply the results to itself
|
||||
}
|
||||
return column == STRUCT_NAME_COL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplied(int column) {
|
||||
if (column == STRUCT_NAME_COL) {
|
||||
return struct.getName().equals(mergedStruct.getName());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinWidth(int column) {
|
||||
switch (column) {
|
||||
case 0:
|
||||
return -1;
|
||||
case 1:
|
||||
return 200;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return struct.getName().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
NameItem other = (NameItem) obj;
|
||||
return Objects.equals(struct.getName(), other.struct.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Struct " + struct.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyAll() {
|
||||
try {
|
||||
mergedStruct.setName(struct.getName());
|
||||
modelChanged();
|
||||
}
|
||||
catch (InvalidNameException | DuplicateNameException e) {
|
||||
error("Error applying structure name: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBlank() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
+40
-10
@@ -24,6 +24,7 @@ import docking.action.DockingAction;
|
||||
import docking.action.MenuData;
|
||||
import docking.widgets.label.GLabel;
|
||||
import docking.widgets.tree.GTree;
|
||||
import ghidra.app.merge.structures.StructureMergeDialog;
|
||||
import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin;
|
||||
import ghidra.app.plugin.core.datamgr.DataTypesActionContext;
|
||||
import ghidra.app.plugin.core.datamgr.tree.DataTypeNode;
|
||||
@@ -36,9 +37,9 @@ import ghidra.program.database.data.ProgramDataTypeManager;
|
||||
import ghidra.program.database.data.merge.DataTypeMergeException;
|
||||
import ghidra.program.database.data.merge.DataTypeMerger;
|
||||
import ghidra.program.model.data.*;
|
||||
import ghidra.util.HelpLocation;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.data.DataTypeParser.AllowedDataTypes;
|
||||
import ghidra.util.exception.DuplicateNameException;
|
||||
import ghidra.util.layout.VerticalLayout;
|
||||
|
||||
/**
|
||||
@@ -72,7 +73,7 @@ public class MergeDataTypeAction extends DockingAction {
|
||||
if (!DataTypeUtilities.supportsMerge(dataType)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
DataTypeManager dataTypeManager = dataType.getDataTypeManager();
|
||||
|
||||
// for now, only allow merging on program datatypes.
|
||||
@@ -143,16 +144,25 @@ public class MergeDataTypeAction extends DockingAction {
|
||||
}
|
||||
|
||||
private void merge(DataType mergeToDt, DataType mergeFromDt) {
|
||||
if (mergeToDt == mergeFromDt) {
|
||||
Msg.showError(this, null, "Merge Failed", "You can't merge a datatype with itself!");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// we have a specialized interactive structure merger available
|
||||
if ((mergeToDt instanceof Structure mergeToStruct) &&
|
||||
(mergeFromDt instanceof Structure mergeFromStruct)) {
|
||||
mergeStructures(mergeToStruct, mergeFromStruct);
|
||||
return;
|
||||
}
|
||||
|
||||
// otherwise, fall back to a generic non-interactive merger
|
||||
DataTypeMerger<?> merger = DataTypeUtilities.getMerger(mergeToDt, mergeFromDt);
|
||||
DataType merged = merger.merge();
|
||||
|
||||
if (confirmMerger(merger, merged, mergeToDt, mergeFromDt)) {
|
||||
DataTypeManager dtm = mergeToDt.getDataTypeManager();
|
||||
// first replace the guts of the original 'mergeTo' datatype with the results
|
||||
mergeToDt.replaceWith(merged);
|
||||
// now replace all uses of the mergeFromDt with the merged datatype and remove it
|
||||
dtm.replaceDataType(mergeFromDt, mergeToDt, false);
|
||||
performMerge(mergeToDt, mergeFromDt, merged);
|
||||
}
|
||||
}
|
||||
catch (DataTypeMergeException e) {
|
||||
@@ -160,7 +170,7 @@ public class MergeDataTypeAction extends DockingAction {
|
||||
new DataTypeMergeErrorDialog(mergeToDt, mergeFromDt, e.getMessage());
|
||||
DockingWindowManager.showDialog(dialog);
|
||||
}
|
||||
catch (DataTypeDependencyException e) {
|
||||
catch (Exception e) {
|
||||
Msg.showError(this, null, "Merge Failed",
|
||||
"Merge failed. Existing type '%s', replacement type '%s'.".formatted(
|
||||
mergeFromDt.getName(),
|
||||
@@ -169,6 +179,26 @@ public class MergeDataTypeAction extends DockingAction {
|
||||
}
|
||||
}
|
||||
|
||||
private void performMerge(DataType mergeToDt, DataType mergeFromDt, DataType merged)
|
||||
throws IllegalArgumentException, DataTypeDependencyException, InvalidNameException,
|
||||
DuplicateNameException {
|
||||
DataTypeManager dtm = mergeToDt.getDataTypeManager();
|
||||
// first replace the guts of the original 'mergeTo' datatype with the results
|
||||
mergeToDt.replaceWith(merged);
|
||||
// now replace all uses of the mergeFromDt with the merged datatype and remove it
|
||||
dtm.replaceDataType(mergeFromDt, mergeToDt, false);
|
||||
if (!mergeToDt.getName().equals(merged.getName())) {
|
||||
mergeToDt.setName(merged.getName());
|
||||
}
|
||||
}
|
||||
|
||||
private void mergeStructures(Structure mergeToStruct, Structure mergeFromStruct) {
|
||||
StructureMergeDialog dialog =
|
||||
new StructureMergeDialog("Merge Structures", mergeToStruct, mergeFromStruct,
|
||||
s -> performMerge(mergeToStruct, mergeFromStruct, s));
|
||||
DockingWindowManager.showDialog(dialog);
|
||||
}
|
||||
|
||||
private boolean confirmMerger(DataTypeMerger<?> merger, DataType merged, DataType mergeTo,
|
||||
DataType mergeFrom) {
|
||||
DataTypeMergeConfirmationDialog dialog =
|
||||
@@ -199,7 +229,7 @@ public class MergeDataTypeAction extends DockingAction {
|
||||
updatedPanel.setBorder(BorderFactory.createEmptyBorder(5, 10, 10, 0));
|
||||
updatedPanel.setLayout(new VerticalLayout(5));
|
||||
|
||||
GLabel label = new GLabel("Choose the data type to merge: ");
|
||||
GLabel label = new GLabel("Choose the data type to merge from: ");
|
||||
label.setBorder(BorderFactory.createEmptyBorder(5, 0, 5, 0));
|
||||
updatedPanel.add(label);
|
||||
|
||||
|
||||
+642
File diff suppressed because it is too large
Load Diff
+5
-4
@@ -175,9 +175,10 @@ public abstract class DataTypeMerger<T extends DataType> {
|
||||
}
|
||||
|
||||
protected void mergeDescription() {
|
||||
String description1 = dt1.getDescription();
|
||||
String description2 = dt2.getDescription();
|
||||
String merged = join(description1, description2);
|
||||
working.setDescription(merged);
|
||||
String description = dt1.getDescription();
|
||||
if (StringUtils.isBlank(description)) {
|
||||
description = dt2.getDescription();
|
||||
}
|
||||
working.setDescription(description);
|
||||
}
|
||||
}
|
||||
|
||||
+137
-27
@@ -15,7 +15,9 @@
|
||||
*/
|
||||
package ghidra.program.database.data.merge;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import ghidra.program.model.data.*;
|
||||
|
||||
@@ -24,19 +26,33 @@ import ghidra.program.model.data.*;
|
||||
*/
|
||||
public class StructureMerger extends DataTypeMerger<Structure> {
|
||||
|
||||
private boolean terminateOnError;
|
||||
private List<String> errors = new ArrayList<>();
|
||||
|
||||
public StructureMerger(Structure struct1, Structure struct2) {
|
||||
this(struct1, struct2, true);
|
||||
}
|
||||
|
||||
public StructureMerger(Structure struct1, Structure struct2, boolean terminateOnError) {
|
||||
super(struct1, struct2);
|
||||
this.terminateOnError = terminateOnError;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doMerge() throws DataTypeMergeException {
|
||||
checkSizes();
|
||||
mergeDescription();
|
||||
|
||||
if (working.isPackingEnabled()) {
|
||||
if (canMergePacked()) {
|
||||
// very limited special case where all the datatypes line up and we are just merging
|
||||
// names and comments.
|
||||
mergePacked();
|
||||
}
|
||||
else {
|
||||
// the result type needs to be unpacked for complex merges
|
||||
if (working.isPackingEnabled()) {
|
||||
working.setPackingEnabled(false);
|
||||
}
|
||||
checkSizes();
|
||||
mergeUnpacked();
|
||||
}
|
||||
}
|
||||
@@ -53,9 +69,6 @@ public class StructureMerger extends DataTypeMerger<Structure> {
|
||||
}
|
||||
|
||||
private void mergeUnpacked() throws DataTypeMergeException {
|
||||
if (other.isPackingEnabled()) {
|
||||
warning("Merging packed structure into an unpacked structure.");
|
||||
}
|
||||
DataTypeComponent[] otherComponents = other.getDefinedComponents();
|
||||
|
||||
for (DataTypeComponent comp : otherComponents) {
|
||||
@@ -73,8 +86,28 @@ public class StructureMerger extends DataTypeMerger<Structure> {
|
||||
private void copyCompToWorking(DataTypeComponent comp) throws DataTypeMergeException {
|
||||
int offset = comp.getOffset();
|
||||
int length = comp.getLength();
|
||||
|
||||
DataTypeComponent workingComp = working.getComponentAt(offset);
|
||||
|
||||
// zero length items can be added as long as it isn't in the middle of some exiting entry
|
||||
if (length == 0) {
|
||||
if (workingComp == null) {
|
||||
error("Conflict at offset " + offset +
|
||||
". Existing component extends to this offset.");
|
||||
}
|
||||
else {
|
||||
DataType dt = comp.getDataType();
|
||||
String name = comp.getFieldName();
|
||||
String comment = comp.getComment();
|
||||
working.insertAtOffset(offset, dt, length, name, comment);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (isBitField(comp) && isBitField(workingComp)) {
|
||||
tryMergingBitField(comp);
|
||||
return;
|
||||
}
|
||||
|
||||
if (workingComp != null && workingComp.getDataType() != DataType.DEFAULT) {
|
||||
// datatypes are different or else we would have handled in calling method
|
||||
// so we can either merge them or we will throw an error
|
||||
@@ -94,6 +127,42 @@ public class StructureMerger extends DataTypeMerger<Structure> {
|
||||
}
|
||||
}
|
||||
|
||||
private void tryMergingBitField(DataTypeComponent candidate) throws DataTypeMergeException {
|
||||
BitFieldDataType dt = (BitFieldDataType) candidate.getDataType();
|
||||
int offset = candidate.getOffset();
|
||||
List<DataTypeComponent> comps = working.getComponentsContaining(offset);
|
||||
for (DataTypeComponent comp : comps) {
|
||||
if (!(comp.getDataType() instanceof BitFieldDataType bfdt)) {
|
||||
error(
|
||||
"Conflict at offset " + offset + ". Existing component exists at this offset.");
|
||||
return;
|
||||
}
|
||||
if (BitFieldDataType.intersects(dt, bfdt, 0, 0)) {
|
||||
error(
|
||||
"Conflict at offset " + offset +
|
||||
". Conflicting bit field exists at this offset.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
int length = candidate.getLength();
|
||||
int bitStart = dt.getBitOffset();
|
||||
int bitLength = dt.getBitSize();
|
||||
String name = candidate.getFieldName();
|
||||
String comment = candidate.getComment();
|
||||
|
||||
DataType baseDt = dt.getBaseDataType();
|
||||
try {
|
||||
working.insertBitFieldAt(offset, length, bitStart, baseDt, bitLength, name, comment);
|
||||
}
|
||||
catch (InvalidDataTypeException e) {
|
||||
error("Unexpected error merging bit field: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isBitField(DataTypeComponent comp) {
|
||||
return comp.getDataType() instanceof BitFieldDataType;
|
||||
}
|
||||
|
||||
private void tryMergingDataTypes(DataTypeComponent workingComp, DataTypeComponent comp)
|
||||
throws DataTypeMergeException {
|
||||
DataType workingDt = workingComp.getDataType();
|
||||
@@ -103,6 +172,7 @@ public class StructureMerger extends DataTypeMerger<Structure> {
|
||||
if (mergedDt == null) {
|
||||
error("Conflict at offset " + comp.getOffset() +
|
||||
". Incompatible datatype already defined here.");
|
||||
return;
|
||||
}
|
||||
int offset = workingComp.getOffset();
|
||||
warning("Merging '%s' and '%s' at offset %d to '%s'.".formatted(workingDt.getName(),
|
||||
@@ -110,7 +180,10 @@ public class StructureMerger extends DataTypeMerger<Structure> {
|
||||
|
||||
processFieldNames(workingComp, comp); // checks for conflicts and handles null field names
|
||||
String name = workingComp.getFieldName();
|
||||
String comment = join(workingComp.getComment(), comp.getComment());
|
||||
String comment = workingComp.getComment();
|
||||
if (StringUtils.isBlank(comment)) {
|
||||
comment = comp.getComment();
|
||||
}
|
||||
int length = workingComp.getLength();
|
||||
working.replaceAtOffset(offset, mergedDt, length, name, comment);
|
||||
}
|
||||
@@ -127,7 +200,6 @@ public class StructureMerger extends DataTypeMerger<Structure> {
|
||||
|
||||
private DataTypeComponent findCorrespondingResultComponent(DataTypeComponent comp) {
|
||||
int offset = comp.getOffset();
|
||||
List<DataTypeComponent> otherComps = other.getComponentsContaining(offset);
|
||||
List<DataTypeComponent> workingComps = working.getComponentsContaining(offset);
|
||||
|
||||
if (workingComps.isEmpty()) {
|
||||
@@ -138,24 +210,51 @@ public class StructureMerger extends DataTypeMerger<Structure> {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (otherComps.size() == workingComps.size()) {
|
||||
int index = otherComps.indexOf(comp);
|
||||
DataTypeComponent workingComp = workingComps.get(index);
|
||||
if (isSameComponent(comp, workingComp)) {
|
||||
return workingComp;
|
||||
for (DataTypeComponent dtc : workingComps) {
|
||||
if (isSameComponent(comp, dtc)) {
|
||||
return dtc;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isSameComponent(DataTypeComponent comp, DataTypeComponent workingComp) {
|
||||
|
||||
if (comp.getOffset() != workingComp.getOffset()) {
|
||||
return false;
|
||||
}
|
||||
if (!comp.getDataType().equals(workingComp.getDataType())) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
if (comp.getLength() > 0) {
|
||||
return true;
|
||||
}
|
||||
// zero length dts must also have the same name to be considered the same component
|
||||
return Objects.equals(comp.getFieldName(), workingComp.getFieldName());
|
||||
}
|
||||
|
||||
private boolean canMergePacked() throws DataTypeMergeException {
|
||||
// merging packed is much more restricted. The only thing we are merging are defined
|
||||
// field names against undefined field names and field comments. All component
|
||||
// datatypes must match exactly.
|
||||
if (!working.isPackingEnabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DataTypeComponent[] otherComps = other.getComponents();
|
||||
DataTypeComponent[] workingComps = working.getComponents();
|
||||
if (otherComps.length != workingComps.length) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < otherComps.length; i++) {
|
||||
DataTypeComponent fromComp = otherComps[i];
|
||||
DataTypeComponent workingComp = workingComps[i];
|
||||
if (!checkDataType(workingComp, fromComp) || !checkOffsets(workingComp, fromComp)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true; // all datatypes match up
|
||||
}
|
||||
|
||||
private void mergePacked() throws DataTypeMergeException {
|
||||
@@ -163,26 +262,23 @@ public class StructureMerger extends DataTypeMerger<Structure> {
|
||||
// field names against undefined field names and field comments. All component
|
||||
// datatypes must match exactly.
|
||||
|
||||
if (!working.isPackingEnabled()) {
|
||||
error("Can't merge an unpacked structure into a packed structure");
|
||||
}
|
||||
|
||||
DataTypeComponent[] otherComps = other.getComponents();
|
||||
DataTypeComponent[] workingComps = working.getComponents();
|
||||
if (otherComps.length != workingComps.length) {
|
||||
error("Packed structures must have same size.");
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < otherComps.length; i++) {
|
||||
DataTypeComponent fromComp = otherComps[i];
|
||||
DataTypeComponent workingComp = workingComps[i];
|
||||
checkDataType(workingComp, fromComp);
|
||||
checkOffsets(workingComp, fromComp);
|
||||
processFieldNames(workingComp, fromComp);
|
||||
processComments(workingComp, fromComp);
|
||||
if (checkDataType(workingComp, fromComp) && checkOffsets(workingComp, fromComp)) {
|
||||
processFieldNames(workingComp, fromComp);
|
||||
processComments(workingComp, fromComp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void checkDataType(DataTypeComponent workingComp, DataTypeComponent comp)
|
||||
private boolean checkDataType(DataTypeComponent workingComp, DataTypeComponent comp)
|
||||
throws DataTypeMergeException {
|
||||
|
||||
DataType resultDt = workingComp.getDataType();
|
||||
@@ -190,17 +286,21 @@ public class StructureMerger extends DataTypeMerger<Structure> {
|
||||
if (!resultDt.equals(dt)) {
|
||||
error("Packed components have conflicting datatypes at ordinal " +
|
||||
workingComp.getOrdinal() + ", offset " + comp.getOffset());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void checkOffsets(DataTypeComponent comp1, DataTypeComponent comp2)
|
||||
private boolean checkOffsets(DataTypeComponent comp1, DataTypeComponent comp2)
|
||||
throws DataTypeMergeException {
|
||||
int offset1 = comp1.getOffset();
|
||||
int offset2 = comp2.getOffset();
|
||||
if (offset1 != offset2) {
|
||||
error("Packed components have different offsets at ordinal " + comp1.getOrdinal() +
|
||||
"struct1 = " + offset1 + ", struct2 = " + offset2);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void processFieldNames(DataTypeComponent workingComp, DataTypeComponent otherComp)
|
||||
@@ -218,10 +318,20 @@ public class StructureMerger extends DataTypeMerger<Structure> {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void error(String message) throws DataTypeMergeException {
|
||||
if (terminateOnError) {
|
||||
super.error(message);
|
||||
}
|
||||
errors.add(message);
|
||||
}
|
||||
|
||||
private void processComments(DataTypeComponent workingComp, DataTypeComponent otherComp) {
|
||||
String resultComment = workingComp.getComment();
|
||||
String workingComment = workingComp.getComment();
|
||||
String otherComment = otherComp.getComment();
|
||||
workingComp.setComment(join(resultComment, otherComment));
|
||||
if (StringUtils.isBlank(workingComment)) {
|
||||
workingComp.setComment(otherComment);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+45
@@ -19,6 +19,7 @@ import java.math.BigInteger;
|
||||
import java.util.*;
|
||||
|
||||
import ghidra.docking.settings.*;
|
||||
import ghidra.program.model.data.Structure.BitOffsetComparator;
|
||||
import ghidra.program.model.mem.MemBuffer;
|
||||
import ghidra.program.model.scalar.Scalar;
|
||||
import ghidra.util.DataConverter;
|
||||
@@ -453,4 +454,48 @@ public class BitFieldDataType extends AbstractDataType {
|
||||
public String toString() {
|
||||
return getDisplayName() + "(storage:" + storageSize + ",bitOffset:" + bitOffset + ")";
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if two bitfields would conflict if inserted into the same structure at the given
|
||||
* offsets.
|
||||
* @param bitFieldDataType1 the first BitFieldDataType
|
||||
* @param bitFieldDataType2 the second BitFieldDataType
|
||||
* @param offset1 the offset in the structure for the first BitFieldDataType
|
||||
* @param offset2 the offset in the structure for the second BitFieldDataType
|
||||
* @return true if the two bitFields would overlap if inserted into the same structure at
|
||||
* the given offsets
|
||||
*/
|
||||
public static boolean intersects(BitFieldDataType bitFieldDataType1,
|
||||
BitFieldDataType bitFieldDataType2, int offset1, int offset2) {
|
||||
int bitStart1 = getNormalizedBitOffset(bitFieldDataType1, offset1);
|
||||
int bitStart2 = getNormalizedBitOffset(bitFieldDataType2, offset2);
|
||||
int bitSize1 = bitFieldDataType1.getBitSize();
|
||||
int bitSize2 = bitFieldDataType2.getBitSize();
|
||||
|
||||
if (bitStart1 < bitStart2) {
|
||||
return bitStart1 + bitSize1 > bitStart2;
|
||||
}
|
||||
return bitStart2 + bitSize2 > bitStart1;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bit offset relative to the start of a structure. This is useful for
|
||||
* determining if two bit fields can fit without conflict in the same structure.
|
||||
* @param bitFieldDataType the {@link BitFieldDataType}
|
||||
* @param byteOffset the offset of the component (relative to the structure struct of the
|
||||
* component containing this BitFieldDataType.
|
||||
*
|
||||
* @return the bit offset relative to the start of a structure.
|
||||
*/
|
||||
public static int getNormalizedBitOffset(BitFieldDataType bitFieldDataType, int byteOffset) {
|
||||
boolean isBigEndean = bitFieldDataType.getDataOrganization().isBigEndian();
|
||||
int bitSize = bitFieldDataType.getBitSize();
|
||||
int bitOffset = bitFieldDataType.getBitOffset();
|
||||
DataType baseDataType = bitFieldDataType.getBaseDataType();
|
||||
int baseDtSize = baseDataType.getLength();
|
||||
int effectiveBitSize = BitFieldDataType.getEffectiveBitSize(bitSize, baseDtSize);
|
||||
return BitOffsetComparator.getNormalizedBitfieldOffset(byteOffset,
|
||||
bitFieldDataType.getStorageSize(), effectiveBitSize, bitOffset, isBigEndean);
|
||||
}
|
||||
}
|
||||
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.program.database.data.merge;
|
||||
|
||||
import ghidra.program.model.data.*;
|
||||
|
||||
/**
|
||||
* Convenience class for building structures in tests.
|
||||
*/
|
||||
public class StructureBuilder {
|
||||
Structure result;
|
||||
private DataTypeManager dtm;
|
||||
|
||||
public StructureBuilder(String name, int size) {
|
||||
this(null, name, size);
|
||||
}
|
||||
|
||||
public StructureBuilder(DataTypeManager dtm, String name, int size) {
|
||||
this.dtm = dtm;
|
||||
result = new StructureDataType(name, size, dtm);
|
||||
}
|
||||
|
||||
public StructureBuilder add(int offset, DataType dt, String name) {
|
||||
result.replaceAtOffset(offset, dt, -1, name, null);
|
||||
return this;
|
||||
}
|
||||
|
||||
public StructureBuilder add(int offset, DataType dt, String name, String comment) {
|
||||
result.replaceAtOffset(offset, dt, -1, name, comment);
|
||||
return this;
|
||||
}
|
||||
|
||||
public StructureBuilder bitField(int offset, int byteWidth, int bitStart, int bitEnd,
|
||||
String name, String comment) throws InvalidDataTypeException {
|
||||
result.insertBitFieldAt(offset, byteWidth, bitStart, new IntegerDataType(),
|
||||
bitEnd - bitStart + 1,
|
||||
name, comment);
|
||||
return this;
|
||||
}
|
||||
|
||||
public StructureBuilder bitField(int offset, int byteWidth, int bitStart, int bitEnd,
|
||||
String name) throws InvalidDataTypeException {
|
||||
result.insertBitFieldAt(offset, byteWidth, bitStart, new IntegerDataType(),
|
||||
bitEnd - bitStart + 1,
|
||||
name, null);
|
||||
return this;
|
||||
}
|
||||
|
||||
public StructureBuilder description(String description) {
|
||||
result.setDescription(description);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Structure build() {
|
||||
return result;
|
||||
}
|
||||
|
||||
public Structure buildDb() {
|
||||
return (Structure) dtm.resolve(result, null);
|
||||
}
|
||||
|
||||
public StructureBuilder pack() {
|
||||
result.setPackingEnabled(true);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
+272
-183
File diff suppressed because it is too large
Load Diff
@@ -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.
|
||||
@@ -42,6 +42,16 @@ public class Dummy {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a dummy consumer
|
||||
* @return a dummy consumer
|
||||
*/
|
||||
public static <T, E extends Throwable> ExceptionalConsumer<T, E> exceptionalConsumer() {
|
||||
return t -> {
|
||||
// no-op
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a dummy consumer
|
||||
* @return a dummy consumer
|
||||
|
||||
+43
-8
@@ -36,6 +36,7 @@ import ghidra.app.plugin.core.datamgr.actions.FindStructuresBySizeAction;
|
||||
import ghidra.app.plugin.core.datamgr.archive.DataTypeManagerHandler;
|
||||
import ghidra.app.plugin.core.datamgr.archive.InvalidFileArchive;
|
||||
import ghidra.framework.preferences.Preferences;
|
||||
import ghidra.program.database.data.merge.StructureBuilder;
|
||||
import ghidra.program.model.data.*;
|
||||
import ghidra.util.UniversalID;
|
||||
import ghidra.util.table.GhidraTable;
|
||||
@@ -130,10 +131,40 @@ public class DataTypeManagerPluginScreenShots extends GhidraScreenShotGenerator
|
||||
captureIsolatedProvider(DataTypesProvider.class, 500, 400);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStructureMergeDialog() {
|
||||
Structure structure1 = new StructureBuilder("foo", 8)
|
||||
.add(0, new IntegerDataType(), "aaa")
|
||||
.add(4, new IntegerDataType(), "bbb")
|
||||
.build();
|
||||
Structure structure2 = new StructureBuilder("bar", 8)
|
||||
.add(0, new DWordDataType(), "aaa")
|
||||
.add(6, new WordDataType(), "ccc")
|
||||
.build();
|
||||
addDataType(structure1);
|
||||
addDataType(structure2);
|
||||
|
||||
DataTypesProvider provider = getProvider(DataTypesProvider.class);
|
||||
GTree tree = (GTree) getInstanceField("archiveGTree", provider);
|
||||
GTreeNode rootNode = tree.getViewRoot();
|
||||
GTreeNode child = rootNode.getChild("WinHelloCPP.exe");
|
||||
tree.expandPath(child);
|
||||
GTreeNode dtNode = child.getChild("foo");
|
||||
tree.addSelectionPath(dtNode.getTreePath());
|
||||
performAction("Merge Data Types", "DataTypeManagerPlugin", provider, false);
|
||||
DialogComponentProvider dialog = getDialog();
|
||||
DropDownSelectionTextField<?> textField =
|
||||
findComponent(dialog, DropDownSelectionTextField.class);
|
||||
runSwing(() -> textField.setText("bar"));
|
||||
pressOkOnDialog();
|
||||
captureDialog();
|
||||
pressButtonOnDialog("Cancel");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMergeConfirmationDialog() {
|
||||
createStructure("foo", 0, new IntegerDataType(), "aaa", 12);
|
||||
createStructure("bar", 4, new FloatDataType(), "bbb", 16);
|
||||
createEnum("foo", "AAAA", 6);
|
||||
createEnum("bar", "BBBB", 2);
|
||||
|
||||
DataTypesProvider provider = getProvider(DataTypesProvider.class);
|
||||
GTree tree = (GTree) getInstanceField("archiveGTree", provider);
|
||||
@@ -154,8 +185,8 @@ public class DataTypeManagerPluginScreenShots extends GhidraScreenShotGenerator
|
||||
|
||||
@Test
|
||||
public void testMergeErrorDialog() {
|
||||
createStructure("foo", 0, new IntegerDataType(), "aaa", 12);
|
||||
createStructure("bar", 0, new FloatDataType(), "bbb", 16);
|
||||
createEnum("foo", "AAAA", 6);
|
||||
createEnum("bar", "AAAA", 4);
|
||||
|
||||
DataTypesProvider provider = getProvider(DataTypesProvider.class);
|
||||
GTree tree = (GTree) getInstanceField("archiveGTree", provider);
|
||||
@@ -174,12 +205,16 @@ public class DataTypeManagerPluginScreenShots extends GhidraScreenShotGenerator
|
||||
pressButtonOnDialog("OK");
|
||||
}
|
||||
|
||||
private void createStructure(String name, int offset, DataType dt, String fieldName, int size) {
|
||||
private void createEnum(String name, String valueName, int value) {
|
||||
EnumDataType enumm = new EnumDataType(name, 4);
|
||||
enumm.add(valueName, value);
|
||||
addDataType(enumm);
|
||||
}
|
||||
|
||||
private void addDataType(DataType dt) {
|
||||
ProgramBasedDataTypeManager dtm = program.getDataTypeManager();
|
||||
Structure struct = new StructureDataType(name, size);
|
||||
struct.replaceAtOffset(offset, dt, dt.getLength(), fieldName, null);
|
||||
program.withTransaction("test", () -> {
|
||||
dtm.addDataType(struct, null);
|
||||
dtm.addDataType(dt, null);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user