From a682ef31ea80401223fe2d4cf5bd12a2d830dfa0 Mon Sep 17 00:00:00 2001 From: adamopolous Date: Thu, 21 Nov 2019 14:55:29 -0500 Subject: [PATCH] added domain object listener for function remove events --- Ghidra/Features/Base/certification.manifest | 6 + .../FunctionComparison/FunctionComparison.htm | 142 +- .../images/AddFunctionsPanel.png | Bin 0 -> 39200 bytes .../images/AddToComparisonIcon.png | Bin 0 -> 609 bytes .../images/FunctionComparisonWindow.png | Bin 118636 -> 99160 bytes .../FunctionComparison/images/NavNextIcon.png | Bin 0 -> 476 bytes .../images/NavPreviousIcon.png | Bin 0 -> 473 bytes .../images/RemoveFromComparisonIcon.png | Bin 0 -> 608 bytes .../FunctionWindowPlugin/function_window.htm | 28 +- .../images/FunctionWindow.png | Bin 25104 -> 25228 bytes .../CompareFunctionsAction.java | 86 -- .../FunctionChoiceComparisonPanel.java | 447 ------- .../functioncompare/FunctionComparison.java | 131 ++ .../FunctionComparisonData.java | 154 +++ .../FunctionComparisonModelListener.java | 34 + .../FunctionComparisonPanel.java | 1176 +++++++++-------- .../FunctionComparisonPlugin.java | 111 +- .../FunctionComparisonProvider.java | 331 +++-- .../FunctionComparisonProviderListener.java | 15 +- .../FunctionComparisonProviderManager.java | 336 +++-- .../MappedFunctionComparisonPanel.java | 167 --- .../MultiFunctionComparisonPanel.java | 377 ++++-- .../MultiFunctionComparisonProvider.java | 76 ++ .../AbstractApplyFunctionSignatureAction.java | 41 +- .../actions/CompareFunctionsAction.java | 105 ++ ...mpareFunctionsFromFunctionTableAction.java | 99 ++ .../CompareFunctionsFromListingAction.java | 65 + .../actions/NextFunctionAction.java | 98 ++ .../actions/OpenFunctionTableAction.java | 121 ++ .../actions/PreviousFunctionAction.java | 98 ++ .../actions/RemoveFunctionsAction.java | 100 ++ .../functionwindow/FunctionRowObject.java | 8 +- .../functionwindow/FunctionTableModel.java | 6 +- .../functionwindow/FunctionWindowPlugin.java | 154 +-- .../FunctionWindowProvider.java | 20 +- .../app/services/FunctionComparisonModel.java | 355 +++++ .../services/FunctionComparisonService.java | 97 ++ .../ApplyFunctionSignatureAction.java | 2 +- .../ListingCodeComparisonPanel.java | 75 +- ...istingComparisonFieldPanelCoordinator.java | 2 + .../util/viewer/util/CodeComparisonPanel.java | 72 +- .../src/main/resources/images/bullet_star.png | Bin 0 -> 331 bytes .../functioncompare/CompareFunctionsTest.java | 430 ++++++ .../CompareFunctionsTestSlow.java | 234 ++++ .../CompareFunctionsTestUtility.java | 88 ++ .../ApplyFunctionSignatureAction.java | 2 +- .../DecompilerCodeComparisonPanel.java | 16 +- .../widgets/ListSelectionTableDialog.java | 30 +- .../widgets/dialogs/TableChooserDialog.java | 128 ++ .../LineLockedFieldPanelCoordinator.java | 11 +- .../plugintool/ComponentProviderAdapter.java | 3 +- .../FunctionComparisonScreenShots.java | 317 ++--- 52 files changed, 4184 insertions(+), 2210 deletions(-) create mode 100644 Ghidra/Features/Base/src/main/help/help/topics/FunctionComparison/images/AddFunctionsPanel.png create mode 100644 Ghidra/Features/Base/src/main/help/help/topics/FunctionComparison/images/AddToComparisonIcon.png create mode 100644 Ghidra/Features/Base/src/main/help/help/topics/FunctionComparison/images/NavNextIcon.png create mode 100644 Ghidra/Features/Base/src/main/help/help/topics/FunctionComparison/images/NavPreviousIcon.png create mode 100644 Ghidra/Features/Base/src/main/help/help/topics/FunctionComparison/images/RemoveFromComparisonIcon.png delete mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/CompareFunctionsAction.java delete mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionChoiceComparisonPanel.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparison.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonData.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonModelListener.java delete mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/MappedFunctionComparisonPanel.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/MultiFunctionComparisonProvider.java rename Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/{ => actions}/AbstractApplyFunctionSignatureAction.java (83%) create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/CompareFunctionsAction.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/CompareFunctionsFromFunctionTableAction.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/CompareFunctionsFromListingAction.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/NextFunctionAction.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/OpenFunctionTableAction.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/PreviousFunctionAction.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/RemoveFunctionsAction.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/services/FunctionComparisonModel.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/services/FunctionComparisonService.java create mode 100644 Ghidra/Features/Base/src/main/resources/images/bullet_star.png create mode 100644 Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/functioncompare/CompareFunctionsTest.java create mode 100644 Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/functioncompare/CompareFunctionsTestSlow.java create mode 100644 Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/functioncompare/CompareFunctionsTestUtility.java create mode 100644 Ghidra/Framework/Docking/src/main/java/docking/widgets/dialogs/TableChooserDialog.java diff --git a/Ghidra/Features/Base/certification.manifest b/Ghidra/Features/Base/certification.manifest index 09122d462b..0da242d8d8 100644 --- a/Ghidra/Features/Base/certification.manifest +++ b/Ghidra/Features/Base/certification.manifest @@ -369,10 +369,15 @@ src/main/help/help/topics/FrontEndPlugin/images/program_obj.png||GHIDRA|||Custom src/main/help/help/topics/FrontEndPlugin/images/up.png||GHIDRA||||END| src/main/help/help/topics/FrontEndPlugin/images/user.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END| src/main/help/help/topics/FunctionComparison/FunctionComparison.htm||GHIDRA||||END| +src/main/help/help/topics/FunctionComparison/images/AddFunctionsPanel.png||GHIDRA||||END| +src/main/help/help/topics/FunctionComparison/images/AddToComparisonIcon.png||GHIDRA||||END| src/main/help/help/topics/FunctionComparison/images/FunctionComparisonWindow.png||GHIDRA||||END| src/main/help/help/topics/FunctionComparison/images/FunctionScope.gif||GHIDRA||||END| src/main/help/help/topics/FunctionComparison/images/ListingCodeComparisonOptions.png||GHIDRA||||END| src/main/help/help/topics/FunctionComparison/images/MultiFunctionComparisonWindow.png||GHIDRA||||END| +src/main/help/help/topics/FunctionComparison/images/NavNextIcon.png||GHIDRA||||END| +src/main/help/help/topics/FunctionComparison/images/NavPreviousIcon.png||GHIDRA||||END| +src/main/help/help/topics/FunctionComparison/images/RemoveFromComparisonIcon.png||GHIDRA||||END| src/main/help/help/topics/FunctionComparison/images/binaryData.gif||GHIDRA||||END| src/main/help/help/topics/FunctionComparison/images/class.png||GHIDRA||||END| src/main/help/help/topics/FunctionComparison/images/cursor_arrow_flipped.gif||GHIDRA||||END| @@ -946,6 +951,7 @@ src/main/resources/images/arrow_up.png||FAMFAMFAM Icons - CC 2.5|||famfamfam sil src/main/resources/images/binaryData.gif||GHIDRA||||END| src/main/resources/images/browser.png||Nuvola Icons - LGPL 2.1|||Nuvola icon set|END| src/main/resources/images/bullet_green.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END| +src/main/resources/images/bullet_star.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END| src/main/resources/images/cUnion.png||GHIDRA||||END| src/main/resources/images/camera-photo.png||Tango Icons - Public Domain|||tango|END| src/main/resources/images/carry.png||GHIDRA||||END| diff --git a/Ghidra/Features/Base/src/main/help/help/topics/FunctionComparison/FunctionComparison.htm b/Ghidra/Features/Base/src/main/help/help/topics/FunctionComparison/FunctionComparison.htm index 5ade8ca421..f7d2246f9c 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/FunctionComparison/FunctionComparison.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/FunctionComparison/FunctionComparison.htm @@ -11,38 +11,21 @@

Function Comparison Window

-

The Function Comparison window can provide different types of panels for visually comparing - two or more functions. The Listing View of the Function Comparison window and its capabilities - is explained below.

+

The Function Comparison window provides a way to compare two or more + functions in a simple side-by-side panel.

-

Compare Selected Functions

+

To begin, select a function (or multiple functions) from the listing or + the function table. Then right-click and select the Compare Selected + Functions option.

-
-

A Function Comparison window can be displayed from a selection containing two or more - functions in the CodeBrowser listing.

-
- -
-
    -
  1. In the CodeBrowser Listing make a selection containing two or more functions that you - want to compare.
  2. - -
  3. Right mouse click and select the Compare - Selected Functions... option on the right mouse popup menu.
  4. -
-
- -

A Function Comparison window will appear.

+

A new function comparison window will appear (subsequent + invocations of this option will create a new tab in the existing window).





- - -

This window can have multiple tabs, one for each type of function comparison view that is - available.

Listing View

@@ -426,87 +409,40 @@

Comparing Multiple Functions

-

When comparing more than two functions, a Function Comparison window will have additional - components at the top of it. This window displays two functions side by side again, but has - two choice components at the top of the panel, which indicates the current function being - displayed in each side (left and right) of the panel. Each choice component can display a - list indicating the functions that can be displayed in the listing below it. Only one - function at a a time from the list can be displayed below it based on the type of view - currently set. You can simply change the current function for either side of the function - comparison using the choice component in order to get a different function comparison.

+

There is no limit to the number of functions that can be compared. Users may + add and remove functions from each comparison panel as desired. Simply use the pulldowns + above the listing panels to change what is being compared.

-

There are two toolbar actions with quick keys that allow you to change to the - next/previous function for the side that has focus as indicated by the pink border.

+

The following toolbar options are available:

-

-
- -

-
+
+

Add To Existing Comparison

+

Allows the user to add functions to the current comparison window. When + selected, the following table containing all functions in the current program is + displayed:

+

+ +

+

Select the functions to be added and hit the OK + button; the selected functions will be available in both the left and right + sides of the comparison window. +

+ +

Remove Function From Comparison

+

Removes the function in the focused panel from the comparison. This + will remove the function from both the source and target selection pulldowns.

+ +

Go To Next Function

+

Navigates to the next available function in the selection pulldown

+ +

Go To Previous Function

+

Navigates to the previous available function in the selection pulldown

+ +

The Remove and Go To actions described + above will operate on the comparison panel that has focus, identified by the + pink border.

+
- -

Multi-Function Comparison Actions

- -
-

There are two additional actions that are available when you are comparing more than - just two functions. The following describes them.

- -

Compare The Next Function

- -
-

The Function Comparison window has an icon () on the tool bar to - change the function being compared to the next one in the list for the side that - currently has focus.

- -
    -
  1. Make sure the side (left or right) has focus for the function you want to change to - the next one. The side with focus will have the pink border around it. It if doesn't, - you can mouse click in the other side to get the focused side to change or you can use - the Tab key to move the focus until it moves the pink border around the correct - side.
  2. - -
  3. Right mouse click and select the Compare The Next Function option, OR select - the - button on the tool bar.
  4. -
- -

The function should change to the next one available in the list from the choice - component. The differences will be recomputed for the new function comparison that is now - displayed. If the currently displayed function is at the end of the list, a message will - display in the tool's status area indicating there isn't a next function.

-
- -

Compare The Previous Function

- -
-

The Function Comparison window has an icon () on the tool bar to - change the function being compared to the previous one in the list for the side that - currently has focus.

- -
    -
  1. Make sure the side (left or right) has focus for the function you want to change - to the previous one. The side with focus will have the pink border around it. It if - doesn't, you can mouse click in the other side to get the focused side to change or - you can use the Tab key to move the focus until it moves the pink border around the - correct side.
  2. - -
  3. Right mouse click and select the Compare The Previous Function option, OR - select the button on the tool bar.
  4. -
- -

The function should change to the previous one available in the list from the choice - component. The differences will be recomputed for the new function comparison that is - now displayed. If the currently displayed function is at the beginning of the list, a - message will display in the tool's status area indicating there isn't a previous - function.

-
-
-

Other Function Comparison Actions

The following are additional actions that are available in the Function Comparison @@ -582,9 +518,7 @@


-
-

Provided By:  FunctionComparisonPlugin

Related Topics:

diff --git a/Ghidra/Features/Base/src/main/help/help/topics/FunctionComparison/images/AddFunctionsPanel.png b/Ghidra/Features/Base/src/main/help/help/topics/FunctionComparison/images/AddFunctionsPanel.png new file mode 100644 index 0000000000000000000000000000000000000000..4b1c60caeb03ab9e55eddd74bd55b360a3e3fc16 GIT binary patch literal 39200 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 Mh{y z+fUPA7{>b#7_Tt9@P9KVm?#%|W8?VXsw!kz26ur2=P|g#VT2#A`6M>MkDUk;&@OYi9CTf0S3=V?B$G)fiURJU5$-c+ z;oyHg#OktURTV6Kji9Y>1&=3Xg9L`>e`94k$pnEw;P1{U{Bl`vtGqU7YPwWkFuNLo zM@+ywbqL=(1>UzZy5A(wHFm%%quyC96kLUdf(|~0zL`VZ_O4cjIW(=Hb?_(DBORhB zvd*a%wsQ)8SYEd{5sb^|F{ZJV~KYFbO0{Vh6(-ZWFW)O7IwhJC7Djfgb-pzK68Rn&#nhxhlltac6=j z!y()s{e|nkFgC*))Rd0SSNpipyN7SPM=Y42Cm{0qJcFc;1Vp!g2hGnyxH%x<+VeeJ zeHy{Yd)XF#l7A8$zi7)FDwft&Q&q}F1;kij0=aUxPN27(K9JU&nu58H vpP0n@d9DS8(>>Olnu62h90;tx8K=!3siJL+pCBl!00000NkvXXu0mjfOW6?m literal 0 HcmV?d00001 diff --git a/Ghidra/Features/Base/src/main/help/help/topics/FunctionComparison/images/FunctionComparisonWindow.png b/Ghidra/Features/Base/src/main/help/help/topics/FunctionComparison/images/FunctionComparisonWindow.png index 572c49532a08332aad2f4800518cfedcf5a88a7c..aa6e53c0fa9e44fb8092c37a9282cae06f7b7529 100644 GIT binary patch literal 99160 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�(%G!1KE3sT%jx2ds#K-u2yogCX zy9X?u9$SrsEUfo=>Vk#zs&X*&n#Vuxdl1vBIK;{c`eHlK< zNmff`Eio1&?R|?r(%NV189ytn1%|$Au&|Wq>RODNHjB?zx1FCZq7u(kRUn$ZxTpkK zvE;fVGzFb?j58hAjq9DhS4@4i(qfsf*WO(43Kfr})^tbek$(X<6(8?4!M`fguy|c2 zYujcTh_PH>GI2v%A7i4RqnQnVjUz_GRWj|tW04FlBS4WlqKOA9<$j!oih>+1~N#%5G3=Yk#veN{+$Z?ZKOjy9U7og_Lx z$mmbTT?|i6f){J2u)))*QUx5VL5V{AN#CBc9Ie?@ar$`M6SGd4!t68S-hM7-1viR3 zmyf5faI&ZjH0dv9HLx`e4kTiU(XK=E6>12v6@$~@$-E7Is;wUlPLv!FSKCySdwFn;j5M(AOyQ@d37vwbs#uZ|Nm;&s_wXkm zF=G(*&S=}4*F^5TtXOPcXwpRP%A4Napb+CwVnA*sxm|22A{-hc14R& z>4hBq7R<|dG~4P2t+HEG6>wZDrl+T`b+|aT*d2N=Wb%IAt7g3O--6vOW9};;S3k~w z#C8Zq)0}y*Z2tRa8@{jC&gxWX&RnzCrDaRzt>q#OH`VGxIs)zow=w<=^GE`8b0%n|FMD@2F5dfqmWPJ#;)}{*_#4 zl9?2)hhWHxFue1}4~v#v$tR%d^@hyg$(Pp(579LBO~6!Q-g-)@uMF*1dhi9DHlmQI z)zV2Id-Wm>D-PE=L*DQIWg`B&qjJf=_WC1MZy4HyA1daT0$(i;uzNOzB4_mL7Vm34 zU-8ibb*5eZia0I{K)6RWdc#?7F?ac^O56i`jX3(lW#AYU%qDylaFNm-G)+LhLFc|N z%rkZdG$xvvV0YwB0l2|+zZ4tNX=&G_A%c%JH@&dlGMif6Z*?CyV-|*o{XwrHR zHPQ*M7rOED`d~G|K(5+)`UyxX*-J>s-HmE=D8K?zrH)37Rj8h?)SJX@HpcSGWhaqR z)Dv{sK2vn4oGx6TP)g1#cw_cn0J}Jum&wvc%~bojzIzYqOPJ+k`nT(7nkCLswb52T z!;>E*vAk`~?>t7!CYv{iEqtEj_c{)p4B(+noGvQI7HV`O{^QK#Ae_-pcV69KXcGL7 zyEkI7CTeYz_-hq+^!WLr%v4%rU*lA1N~!k8vsEpY`BW~}v{0Gk5Z8}9qIvv^>X3q_iVB4PR zM~E{K9~b&TQ9B;5+e!0RI1ys@1M-KPzsj6$^z#jwJbL-PacjVqa??3RjZprthqM-J zSJ0acraFvkXvrR^mA!z4@vKbub7lk1GDI!4`KxBYqzCA7=!k#nX5|mT@7C#|v6+*psb~HTS-X`;WoI6SX*hI6;t4 z2f*oRfa^5~tX37Hp!D@uwjGSHU`wUif->{TO0hTZ^~;Q6v+8kdy+7F+k2suzrDzF) zlnX*vHSAoLd%#TKlcj%o+((@%%U*^Ay(}*hj%aKOe&+vH*jEScg2qE9333oF(GH8) z={&=&(hLU`<4&AU_g-|1)NcHm|MKcwoulOfi`Vm|o9nY*?FkS)8I&T&b)rqt0Q-w$ zs+k0Mp%azFE2j;#UG$Z2T!uT67>1fA4^ci#Vx$fK$JvWcO!*^roCyj-bAv^w-Z)VI^v4yo=Y>WE z347F&i8MweP1KTU*jm^I@W8;=dV~?`$o`7`$d~S#CuxmZTPG3GlF~yU1=i!vc2CgR zd$|H%UN84ba&SHc$a#BEMdYIsiq%Sw;!H-!^IeoC(B+{>-u3IfiJQboPORemjJ*;C zx??v%@2g^@f0Y3lh)tJ0CYEWaH<~WLisnqe|W?{nNNTH23Z9g~ zDppy2A40zt_BdY;*byNRskplI9Vx}SHhM4bql%6`9-AO+pC zKpw&axXK2*|AyZcO%C^_=~HJ(AcA0=3UhAR%-1{5+_pLNZ6pap%TaThjuG;f0#m~Z zfM+~{2gQ&euiJlsm15cQ8u&k{2l^myZ zmTuJUGMUzZwVHTb_t;IgS_7b02YbNH@JCDM)jC?+Z;sZxU#O!&&K6Sz8n93>XP3{e zFG}M07b)`|rS2ww3w7aziC)=P7AnKPubMyxQb@A(wNe{TChSoo@QJGWIbrdvp;}#< z!8rY60+}}uy*A216~kHx##%D(CEx&?2)AAI)c{B-TcTCAv|UU`uyWDDJ5HGQnPJeo zLLMD5Yt9r1-WD1$Qp9QFOLE!vysPwc^~a3Q;N0`AI!*3n$Y9gxY9Bij>UIbMn>AHv zkYo}^g?dYc9|k#F(h0(zeX3CyHqtqxg!=z7yNL@5rX>17u+u^#G@|i{f#B$Iz^J+$ ze=?^5QCPlxSe{J3A}$IGfdmk?bAaS9yEWQJW9Rq0ToL;6v`>d1KddmKFk_Kl< z^No99s%RSIB@DpxCRkJf+Wz0bUX4Z@kfXID4Q0LxUtWh5seU|(2H&dpKWv?8AR&pt zxb5F0Tc3^w<4u%_spmFbBkem{{WkM~RU8eS_gQEEg{_4DY`lNKUoqSkvNZHO3FAyzMv^xm-X-IQ8=1nEgJC_N(ZVl3g&hjoJC<67l0LbrJ;dWGH{lk8g@&h zptXyNeLLyDyAmIY8u zIE~v9J{EvMyr;2?+4VI+Yc1_j%|18UISNJlEg9?^-%$y%TJHhYCtX-KfwW;QRkz6S5$)xK+2cB)V0Q42WQYI7T1x)~Tknj*)!+-@ zA;|$m7P@B6R0i=kF=rK;HFQ{n4K3LgiELdomg3l>$*_AsyyPqNV+*!|Bn7{HPnzk? z##v!sk{CkG7%vJH3S)sE1Wu|*Vu(K{#N3Qd)@m(Un8RY_cGsUi#ry*dfE_Y;px@wP z(PC4?Q^K_vw>IEmb!$~nf zr`;8Bs08Q~J@#T|cC$@ZyR!RgA8h!dLcnQdzJUUzfRLuDfc@QNpLt)}D1E|WZQ_4d zWx0%nQlwcD`eL_g5>46~B69Lhnb!%oW7QAX;Inv%=yLop(M#(XIe+{wfjvWJ62 zO*s(ZHfz76@!EsA5NuYj2d>BOI_xD$jW}U1G%0f<{EtSki}#yT@(>F?KJ@>TEp-xi zsEg`9PQI#S$^*L!m>hj@V(7zZILD|P44`|CIfoOI`PRp-4sLf~i$D9tlmoD8#`oO< z9bxT73z(Rb0bYj3&!~WCs#9ZIQcBoV9)v@+^?WEC!bm)!{vLe4%)NpFWC}m=H@WWp zY`bx}VaS0Q;6XhyGLn3ArGL3-Aj96U^H{6gtVMyn=~!*|(kVBa^(%l?=P@QBkJ0%% z`q&1-qtJ-mYNwtTOnLfyj+o?+nUk<-=-R~k5}v+-vv4!>!Z?`-gD~3l3UyuUlb;I% zan2E9*vya!*|>hVycMT~R!|PnF$7T^`rk#$iPHJ}2wXZ0w4u(>*|AjRp3h^g4_a=i za~U1FJ2yG=A>gHx&m4JIW-2Etlfj0eoW779AUP?fNw@j+IzrxBLJXDINCi1_?Y%e= z!|IQl>LHL!vnOkK9l|=_mVtz49_ma_Va<9s6giI;2Hs*~rJ^-JrnLzG4dTn;)q>b^x zexJ9!*>haCo%HUWY&hI+p{u;b&#+kJo~1Ooe+T>G-N-b+P(ed7kn6&|%gaVv*c{Cp~O?xXY%)8rIX=-nqhQhsSC2;7HwBJx?QvOR|zC zu%oaWf6c#~0(4KL1Ofue94VkYExL}{d+BbF+u8bH13$>ow>N<4>3a_aJscFwCRkrQ z)eBNYK8@peVUyH;(U&_gOFjANJaAOxNTr{{2u!_yiw~1Obn&9sLiwCI`9vE#>~Yy@?_U%ZnFs*CX9U zA{*i=F1!kI~MI*BRaw&$NP?#%;xZ)BtqSv4eDlz*M! zH58Zi)*xP*#_QehN(m3`*SKXyc?_9Yg(~OxWT5py1`4grR~|?`6%={?QXnE>QF#9J zRVqv%h<9F=lLkKe=$m_VJlZsdCwhb@L6ykFnSBff)k96tq_E-C^R7A9zUab!n$?Eqz22-bA_p(1g#vipkU zx*(ulJm<9if$81j;gT56ggwHcLc_s0ziNT-wJe7|Y1(D0^FPR*|AdgYC@im%Mq1l5 zJiXk=Cf4Cebcx@ZWMdgEYu_z*g~!Q`zzih=6QUiOYfrv7Y_kQXOOo=1EWB}>ulE{?Gf2_#(E0e$tn>2YGR`tag+#pa1;5M` z(7MrGfWZVDoa5*P8&H&cab5#uL&-JzMO)nAYLVq_|7XiZGm5{?y=m6Y1DlaA)jL`F zy+qiTSe~Nc0vpJb>|U*&^;U@XHDSy}i=U+gnK~W4Nc7E(P5yg?BF|KX#d<`)=YrB7 zG2QsMyH&1hz7>(_`wt)B@_!>!?rPXTl%VdmuSo)qdD)ag(O-)E0ea{kHt2}wk9?=Z zK~J!VNwGM8?I=^e!DUVKnL?YbZ=z{m^&I#Y2tW!tNG@vPdXPPxJ8J#iw%VV-l5O7; z^(_5oXdtru-#f?uYCped7QT(du*k2w5B)i_^1`yc$&e_%j7%AL#Y{HsPXCUT0U zuj=qlRXJM~zIyOaO#Lqh#rdE1iP88d8$E@1<*HAI8=f;w@%z#vOlRO zsl7B+yODB;5LL6>wVsN@C5R35%p$2Gnvoc|_?On-6_GtWZuEox=t=eUz~TnZbM&3W zzs*F*Bxj8uU7`%iRTOknWeD9Z{S46GpJf`Z_QhF}KBIF)+dJs)7)pA@q7j@{l^Z?A zq%uT@_!KX7G%~Omp%GLTw-6FP;+K7&Z z*6RLT2d)@pkjMB6(Uws`m~&%%+Gzi3@w&-ekVTw)$e!cUZa2A^Iw2~r8yzXgK7&mC z->Yg3c+CHp=mmYG=*72pB{~{%3G8gRBF9mB-sh``JVlDjJTlq^k9`q~>GIs~>1rrN z8bz88m%BAW?smJ2Ee7MtPEM7}piQFMZlVjO=L=gt?AClcqHG;nl#V(6+9?t82~cUO zOt^jI~N|RPIby6s3dK%qL6Jp0H@bW8xQlJ@oR1 z1fY`H3KGJR;iv^or8&U zg!@z6rY7%;O{Tit>e-yvz7H(jz!{=m^mYr4hrc^;i!$BUc6DoP*$li+;$9pzU+$@Q zJRLZb3oMC85lJPI&bx)*fm=e z52cIfzIgWyQXKqvY>`ETkk(Z3SbS56eqy}EW~IggAoMSbP69&_R@zxJKD+7k=ML5mH<$hd#tVb=0`5A- z{l4^@O~R^td-4vcByI)$ACk(Q8Wd*j+hdv;*8+H`YuiuG_AS?i_yz56gm6EPrI7Y~ zo{6R(Fk`agHQhh(#~`+%3meE>RX05xvBJ5wdKAkWc6&(ZkX zuUNN6NwwTq7J4$S74Au@z*!9>Nc!0ty9C;|pPs940lZ%nHU(c#euj79;7FrL5c2wH zNUYp){Dn#CyxVek7jy=j<+2`7yb(O{rNa**t)5KD;4#54-eMYneJ=^tb!l=x!RmN? z;(ye%5+gs>Br#U1uS?EvYyE}r16A-#1Y~=YnDsEkzMuVW4n9qvqugW z)_z|szb~Ae$-gHsKJz1GK8A`}2tz`x=SiM7TprtSSsDG*0dGY=4OWK4+|o18^1~KG zh`MvEvYePhlqiLS*&9DCY%n?7DuiQUTqB|ZXp#a7q zd`y_o@i8Yc9JZ)q-I$)pQF-5X)_<90y{l5)NvTb{~p}Hk(b0qNN(D--|r?j#<-h zBnFRbFG2Q~p-jqMWvai}Z}Ro^&}|r{E!mOO5$Imsre*7mRXM?At46n~rK(R#~3h z?Bb>+pQ2BMaAdAS&2m5qZ>C+3!y2dE!2D2(_(zNkk&;*K@FZ(UG zV9W8s+#p=h!yY=Rq@{;@A2N}l^&J)n zF_4Y+4b+sbY<`dNQPk<<9j3AP3|0E3vIMX|`FQYTj+QI661(a0w@KW{M*{;;cMb-} zuUhzrc}i)PIhp7L2tK=o;ycCW0PKLSG<0KNCEgBvS|0)VZaP3-rJS#nwvv}(tLB_7 zO>bfp!7_G4)6~(t;VKK&6qOO4U zFk~ryGPmjabsL|i&4o<0O0?lQzUl!G5(6J!{GBQ{TY+lvIAc4?kPoH=G%ACdo30M~ z`&tp}?h>u<+;7g;83YA&N@#!%?p^OKK04w&J)TuNYl7c)?psjTeT6>o_xEqB*w$j) z*V9~A3i8v{M3V7bI+!T|oPEps?Lz%Ye;=v@>6A`1#=J=c_}rspx%P2(I?CLQSJqz) z7gc#bouHAyO<^e!giirV3?X_j=nVms9gxEoO%B=+a2k0_!W@g{$nAABz>lMj{(h-y z60CkCXxQ!o$Iit?>3nIp_BEBlwpRRv%TL_DwR4^GBVtwvIb;QHq*$$)1D>Thb zMDD6DI<-n{9Z=7XB0BoP>0Wvn!G}ZV$sYt2w*Zvey&4u8 z#w=3!92f6&c|d*Z@<~CrN}8bZ^V|m+N!K zzi|4ST4?Mk3BQ(N>(KYH8I1lx_Xr9%h2P0LH?e5|kEme!3Cg~MP7nCE%nU)OFOdbM zkc)!LJ`UDMdABbLdKeRO8YQ*_z5g@4(1C-{>=W0U zQ*w~fF}>L;zJp}oAhClc8BtG>pp=7G-P9B^4;Q^xU*l3f-BNO8ZHsVq(9|rfah76F z>paq1P-5>4n!kO6Jel3H`H~{KVOG32vQj&zXI1ZV6&TsR^7u8|jZ)9w^H3kL>{v!QPpJJLkPLo9<6$lgMA1fdA_xXCHR)jvc zy%6awPgQ+BZ~6I3)H2~%-|fu}b&dKbPa!$mZzRi8gB%;qShm)Q^rt>P69;v4f6Vo_ zD(!v4YS64l%&upS6edl2ZXa1@#p<9>_o(rv`-CjSYP+lVc8fLEs&F92N>rPE^162M zdw2%tmUhvR)fS`ds*5z`-+D1e1_$lQkh=Bog93 z#q6CGfuqjJzo6h>hs{BM)O!~cfNu|Y|HyH79&i{^2Fxe@>pbAl6Z`+K%#U3|ix-Lf z*OkCUQTG3jixDA!75i?kFR3=~9vK>jX$d*moz(&+3``6MQw1V+5&ppD=?f#{rrccb z55lGxbGDhQ9f?NAp?vjpAelE7m;vd(n*%vFRXd_wA53Eb%3k$;pwjs;htB=-<;#fc zA@u9!8ryjt_meG#{sazJYstG6-AmHu*qofGw$V)z@u)_;UGo&%pr=Ze2P(Bypi31G zBm;%bd=U@iD`b^=rMYT>R#`foNUn-B;Of1F0Nc3T%arjY7-+fza~$TJ#yxo5E(O4X ztc(GcAinZ&wO<~X+o7CI=UDVS3ODYLSKOK?+3rEPE7P1dhQ$Ftdwy4LSmsvPWydD3 zpRJ|Xy%_N~3E^zI`5Ue{LjJ(KI+T*Tfoo>U+v z3Eza+?bVT!07oV98azqeWp`$^r{e(NI5uRInQfiJFK>WJB=YSapNL{(7~}>2y+Tt( znC-k^9gxH|B3X(rWtlX9f^4txNGUWTf)4?Vrp5Vc)H_+w4U`Nb+FP?C0PZA)=%B>+ z#lAU zm1&k3%iNx?XD~=dP!`7Fb`h{EaFhXcLi}|!+I>eLzx7}4x7cllKG{0ES7JCtqdz=? z*}tIkST%_K;Nk*U`buF@>0=!;q$Ack<=h z(|DhWKavw!EKwMn_4c#hM3OB8RS1_O zd^OMTTQ=bF#v1Ikz9-08yZ$fC>QC0rDTg%{SS5xTgbkWKy97J$x$}!Fz*G2@uYb1y zEQOS)x59q&r7ancdDPBAlkVD3Mj2ux8BO&Npe$J0K2C5q2|I3+R*RDX)>rxJj5$Bs zAtLH_@Ph=~_x`hwHpjwFk-vLJA%)t-rC}>#r)of<3~RKW=3-#< z8}P>~^x$Xg=)7_7CuPINq5^UVjGxPzFf`v z%&sP(l(GL{@*O}G8O^GkJ~*)=iNn)ySu$ZC9bELeWdnjjf{90^>DG3l4+>k%1)d7! z_9vE7`!9DvFb5R~f=e^hUdO7rB9NG`=w#%bS2?l28zLQj&)+W+=aE;!feoKM;Xkeo zJHq&1TJUoY#g&dqdi%JZVbNvQ#*$3G?7&FaaZN$G?rCD}aR2D3L5p`&bc@TbP8^@h z4yYT6Tt->Z_t>o}tit~4A&Rsc`xf2TZJYy=u3I~LO=4dukv!_O(_L9MJJg09Lg(0@ zPQ{NRUly64?k-oAwHZbu240n1$sB(&zuj{73p4u%e(gB#o<|awiSb9Wq}5|* z4wWF0MMbxy{)a%x8v6kCagR}?NM36raa(own=A9WvB{C$w03}nmT`|xI*F;{$HyYV zGU~+R6WgSpS8(JWDBredNT9KLj zzzw?>fWD=$uSP>iXyl|Ny-ig=oNYTyo-ZbXQ9JHT-_DZiasPYBsT5EN=h)8I%fYj6 z{NqAp*Pk^Gmr&~;FGAO;lryE40K=?E#Au%o4D;T1dgz1~^kXwN7sxQqK#nbKJd`uJ z*|12Yz{GZKxjOtDOBnxE!&T+0C4T|aUCzOqjnmre&K z)4ewo6EA(&fv~eO#WeMD7Z4ogKOwF_TZpO74@@TDJx}~XeZ&PR*AniPvLkw{FHsA* zK@rA&R06&5wsoco{rwzql(A;hLwlQ7e64fpi9)h8d>i~}0oSV1!$PmL6w()fTDJq^!O~dLe@pHl zj$Nz*c}}hKX=gJU=Da=e)#P<0Dj~BV9(Nm38Jp7%b*(57m}ax2s#wow#8aU0{OwC8 zLhw6)>N244UWvQARVKyKnDg`YCK9(k>>#U|(cQ1Y*QvuZk9KD75|uX-_clYzCVip? zDH0u}r%0*2fLo}IN7E92@2}ha((<%{m=N7fJinL{K8Nc31|U_f=mWv*(XZktyiiX7 z&aHOfihZFJZ~(2jKfQxHU4eqkHkp3_f-2CQ;}SON3X8rt*)C!d$=ddS99^W(oy%4zU5 zS!7qUoG6yPyJO5VSw+Q_!0XySHzUKZXzsPY8|z6sR{z&KIX#@2JPI!?X*s?n{C!8q z`1oV`%k2~_E6A>B@|VqMe7Yw{r@P1Ge4KAsJ>DF3Sc8+@&4sZ5f~qHu0+9CSFkN)k zXV}=-8?Z4$)0?{`@wB7Q^ro&^p;!S8b^;;^2wcluK5*VAJSgFIGNjFDmfpuWEm*7yb7yzuX@a;L}ZEkL7r)`pa zQhYhclLnc5Eh9Hobqnjlm%ccMS=_#q)_G+;mr%bhE|dHV1wUR*apkS%q`YY(Pc(3= z*G(%pCj_#9tQfMWo?p}87G&+MT%XH}>3f_|wxc~Gdk1P()WS%`I(iK1*#*th2RoB6 z|8T$VV*FePP1{d#f(G6klVH~-PrW1Vek2%r9ULC}K$$3BXb(=uKj#*UMa?_?p>fQ3 zYKp%1wpL~V{movJat^eAr4gNmxb6lmTCe7lixRD*l+dX%Gocxyg)^g0n!wQhdN($sWn%4TN0+V|>go0ZO{FBKH$@*}QAKD|L#e$~>*WVtl<*-<(9 z^<{i)Db-^gP_<=NpKb&6I~I+m zGByx;6hOz)X0QT=6J^1X6e+r4_x>;avG5AO}eQ~%`Ez=*>l61Br8tQ}n z*?XK+7{#Dd-OCGqXmqYkH=DOLVmY?D))3Q|IlcAM38VzU~*kegs8Ck zpxs~*osA{LaazFP+~xAFe^GJkSXbQ}bYm1rk1Etz0Bfid}j z9754yrL{`T_mHHwnmNV{;&~Jx$An__?YEyqIJESjxnZfC>ec%K>~~PwD!tifd5vwg z+rUSweK**EoMLmtL+P6qMp@su=yytP(83i_%1U`ZM?%`AQh{>_8>JomOP%F)KAO+B z_^zVTdYwZHOVhL3&8}uJ6g-zKRF`Y-XdUCyl%=!3Gpm@MMxS8ttlX%KdnmkRma(@9 z`c{Z$-;Y$p-t0b?VHf+5u*Y;#8pO}Kd*Qm!8Gn9h7ixPMPGew@ zr(m@;H3&!g=dU27IC|zb%&WjTvy%=#_Br}{oLP*o7dqse24?F2LLX4sB1b`thh?iZs;5y3kXaQ$O~w|?4TnG9SkkGOyVRSe6?s>sGuLk$M=n_Yx1 zap_FfRwowp4ywIrLP8)i>hJcuy3~Z=%evay8giWN9RT`z*ZuvP9aqh@lsAB zi_Sm81T%`tnx(3Dal-IV`;H#8KPvHHn`P+Tw)@0*SbRz~Kj35W#9R@p=A>-0mA;NS zA1S^OoinZ&gGCifkEw}v7;h(o*iv}sUh8MQR7k%o`VeU`$@?dcppWcHJ{23|HI`aq zTLdE3q{1&8Cb`fUuN=kh*CbXA8xQboZ3Nj);S~uP)4nir(Z)Q5)Un~G?x)mo{BA$d z3mb%Vh>UQxgRv4(wda;95M`$n<;LT$q|Z%>_ddm0W6SjnO3s)$ju=DjWE^`Zn}eio6wb4i`*SjiEE6m!_Mfmq*464q+Tji)iF)5E z0VHfI0tf&2xMHZ6hHb`N3?e@^_sAAHb$U{6CtK4jIqqPI!S#E`ks;@T$-rE!mO{hz zoTUt&`6zsaeF)FUd}K!e60p+qAr7wH$XDD5QAndmWV!Fi_tm6S+Q-VfBhQz(QE2Z8 zL-EdK!vUfw`FY{e%?-4v*xZ>IgrgOum=v2ckFHu0h$~D#<0XUG^EW?z7Dcl~&P9iv zGB~>B5T$c-a$1*+haKX=KXP&wo3>iRsG1H%9&!ueX%aH=Mxn~1k#3ZP2%S<#kyXsd z43#qExJHtgEFoI)h`hw$2b?bO8bpzQ{BD@%kyFX=~~8zrp-y1jy1RO)I&f zjfb%Pz+w5X@@3)<`(CF)ec51#mLNFB3F2J5>5Yuw63VWzQa>JAJbTycKk7co%zLU+ zxtW6)whYm%175f&tpCtY!R7u*IH%xV2=&LkLp(JWzAjLoE1^CZI+_*V&;9muf12w& zn}N5M!DAwJ+9vdHHE1ID1Bv3RS(A&hvB`t8OhhuYCKpU(rVWWjddZe#!A4$N%nX<4SiU zgo*mlQ)Au*h=wmeHs2~2& zqB12rQF}VSP0mF)Cclmj0HDnP+{##ifm5`2?J(azK%@v5GaWNZT*hJQ0h$?1*^C34 zjvQY<*Xx4U(@|ENFKKu_-fLI4xAeIw$@SzPAyT4#t>qyMWwP9KpC)j96W3ztmT1-dD*B_VkN}Du+R65OTOw36liQEa#(F@G9e6RX(??6(@ z&N+k~e)7>KA0B!Hub`o;AdGlADqi#~K>O8YN#p{P`Z_yzP}Ro`|NJjeFQzut8OncE z?#EcAG_z+9yt1V{EbRzH4AO1zuLc-E#6$;y5gxld%zhn z07U+$?;>;<=cm9A zD;)clJu&iQ{)#?DOD+LB*{o}Uf%*Z?tl^@ZhE<_SPGH}tF8%kLbAGX&(V-_A+ll-4 zv+Pdd-KxCiGsp`kltS3cgfF4VzO{8Mk&IwL)QM-dhteATQPyc%`!FVGsM+@KUPa-sl{+%owMfVM>oW|W0=Q6loW4?#CEtebrFs};R z<=dZQofje*xuK-uK^_qp59n5FWi*-k=7*;zfA}sfBmX2eYraLZ zedqIm{#dn4_k}*&qL!m5gz5pC$Ff?vFuV2+{x5mYr|oL$04k3=Cisnb1BRL+bT~F4 zJ9w(#wjK#&hDj>xhOG|H#7Lk~%y7Hwjwl zB>I;Rb$&jzlI=eYbMJ^mfL@Wp5wtA!R3Fx~)I^??f|k5;Id; zeB;lPkOzt*cd@r^rkretoQuV-gtAOt52soKzBS-v#9i%L$yH4IN{hSd3#wPqN+ix740#+b ze-Vu^BKRfr|JAJ|$Ng0KZJc?k?tHKVSkg2QaNSHWEq&T=Ul*?Yk=DbP^}c~7!VKLd zRh;j@mj0S=0m335MVQ&_t}t@{)~NCobtFA#qCPHV?x_8KWK@?YRs&M#NbRxCckqW>> zVRxuh-lp+5hGa~BkViu80T7@H{sgFSwD`!E6u=lkj2dUy>cp&r{2d^N1{gCVWC9eE zd6?fM(P3xB@shoKSw{Ur%0 z`X%%~mt$@*{Qb3wbqepl4={;(<#`4Z>BT5(tPBA-E?G6T7!iRTQeih(sxTChPRWCV zM*TXtjZUN;cc!TU3CDTl&{!Gjk^O>U=bT=TC^W(>l-qS@Nj!6TbCQszFSzbxg zQCB1l2hf{{7lt18DDuzKtLG_vUkRoF27sVHz3^%Clu4LXUaw>Zzu9Xs$dL?QX_N?j z)4(1r&;CfIK+ORxCt0LaBKo$d{~r5zF9tp^S)|oKgH1GD&O5xP3OTRePA<4Yr#83B z*~Fr(m0BH0C1V4AnPPaspaghnSgh()XA;(D%t;z%-+@cZ%F7!K*!yNwo<*y4%)5pC zdlZ{4-}1E~?p7|GBC`_vL~Vm~t|irJS7o?;VxTu8w>I$yBKJDB+|z=e&Sr6;3Q-}i zD&XxI8O>+3HN@p1MQ0=e$~iqC#cFw@W07zFdbqU@qMD~b#t?;vP0kagYuBVfKtLd| z_6(+140_27f_;4(700Tr7Fu%Gfhy6d3OiPTMhYK>U{MLaV_U{H6a&}dmp=7CYDXh? zQTkalL$3CHXr&|3CZ6?lh08DS@k$gaotFt#T|C*nPzj!}AzkV=N#KnS#57{Hz%%wo zLw%Si^7zRHy<|`^?}XXFC%#p*_7Vlaycqmff5V=5(~3`1CBTH=u~cK1BW&Li%B5;8 zD7JEk?@L;2$M+(Q>4@K4(-}!mM5%TZf)MM27m}t5=dx`35eCn^JiZ{jNg_biA3h(8 zQu{9@{PzF5i$A2k`XF)l#)~$-l7fSeTX5LltfPu+{!%jlw9-y}v=A~tB zxdn7Kv`X$~`>=GQ=Dizw#tM8_>1IwFUFr(QaX*Oq70Z)gpo|P^tR$JeaoLs7NAJ7+ zm!h6NF23Yy$K^kGq7bz^?WkAvgj#rGo@k*24kx9MvW{w7MUx-O5;1ufr|RH&`XSDn z2l*}R(l}L)2D!%>YCv9waD~r2i)~3~y$0_wjczL^FfS@a8}4JrO6uYkTz#nO>OHjW zH)*v(F@C*yzucyeZyChmUL56HhWpM0$H%9(lP#3{5O3KkG9k^;wQq{tXq-=V;6cW@ zdazP&rTb`(Yv=;kH~3Teq)gfh1DcMO|7>rcEe1zVK$&U{E`Q24tDuzXOz>l>Oe}_# z?r!!tYW+_=1+l%$R)*AVZ~T&YJbz!7Lr2|4!EJO+k=>9phO0aBhX>|K2@IiX==UE4 zZO+z|-OUCkNqSuzAHXF$J3D27#-m2Fe$=KRiT)aFJ#(+(|Iqf9L3K4-*JyAOoCJ4w z3GM`UciRvoxCh<1TX1)Gf?IIc;O+zn?(TQxk@KGK{r=plTepf(l^=VvR(H?tG3OXP zrs??i-;SR)t^o=omP90|*r|@~SLW8wuuA}5T5jK+cVdkgBB&X%|%M>OLRJJ<~jPr?Cdf!7YUXI_!M+p*|pyJ_~QF>hr$t%~Hxy+Wj{_52l z@p5lUNLeIx|5A6wFssoZzeM}i6rJ2zsT_Y1!>q)Ah-GUR$H8#@t=6Jr0gJ|%g;TxT z$`>3h^ZfJ$u&_Jdw8(Kw{xiDP%$Jy@%Ga`IwUy9N%K3 zJU?K+APa{4RUlMkx>sS^_r2Zt#<@oQM`u0-1lpH>0ISq|ce^d!s8b?I+QH%Yv+5T)rQv{LqLsdY?|B5z1EUfx7>W?|bECB)O9=Dul zo%Qk@E6O>*cMVm2?wR?M4vL1NQ)~m9bF^Fn=Fv=7fy7qx2+ka$Q&^XXW#hUGW%mc!k-M({uXk@XM%zw;NipgSXOvI#<a)M;O6$8aZp&DE34c1UezO-U@Ua|3zL_#t5!5nMfOgVUL zKIZUI2$5nLU&b1@qc4lxrbS1|Z6f`ucw}k)Ry9_;nE!j8G?+-3g&v=is}4G{LBl^b z3$2Tjgy9eiw5IU}mu8`mu5#SUG_Dd#-a~EaYA~EKs8G$apVGqJ*eYMDF=#EU#hzg7 zd-B2dFm1vCi6j6Pw-x|t06G)9#Y{Ayn=50h!_9@6q_aJkDT!s$7ce1)_?i?PUDNoh zIh2uq)=UkU%kMQtQWfj9v^O0XPmOdl>Ry~ z)WS)%v2bV*fK9Mmrj;8VJ=VC>XLgC-n!M%ZFeNYe&wMa{MgASZ&#e31vg^C}I4N|{ z`e!ae#7r$Ds4rt1oNyzP$$1uoctOh~24~5^V{$KFYChl5AJ)hs?e;V%{mhFuH|Fp* zIO}C-KyC@w$7EW~JShxXP7$&#QjA%wjN1G!fFXDtOIX?dP})bI_n!d|o{w&~&$B3T zD{8PxC6>W*iqF)`it80^yLF0A5-nRVa{hDd1`q)`}DyU~pCIKycs9Ri@P!HoR1}=l zyGQYDOujU9rprD|a5<*)*(qNuP6aQMFqd0DULAkN;lq=#X1nnZ4ft%2jXqywOvEmW zY=5vpV?w%!M0m*q$)9bJh(I+01==8AHHtX7wZ%qWQHU$ppClyv38(ch{& zG3>6$WU^{E%`qdBLzbNL_4nw<{kiIB*(_y!{&N%qSN)^!fwV+D_d!=om57C6m&=27 zLxP$&D24h{2d;=g>pcMCOBaV4*;N84%92ZcBAc)FoPtCBbmRCpjOVC!3&@XiQ&T0hFWt9<5B{G^XskMQ&*D`^4wf;Zdt<=QC}S zsZLR$&IO2EwV%-!BF0*~CKfo3>`qK8dOw#xM0WX0O^jy&wmTUhm!TcPnK48TBMED2FvjNFO6mTD+EXQpxC~`<(a+N=w&v5|;r#LJv!!Zr%eEbW1I2_j;icgAScVS2 z4K71Y4m1v=Lbf;^X@FKHAE{g_^wgv$fkLlPl3Rsl^A>WS?0S?RLMs__DEPds;C8vpsomRcowe%FeYApIG3u^nP&iBpkuN792-qws2c zOdi~CZ{ZYXhcBF|6%9xxXT-=>0@<>JS9Z7;bEx~T(%#%vADO)|0Pzgw!6VdVYx4iK z_)C&dxsnL^-8H9BXJoWkSN_8DJ~;ty1)~NpB{(YHF~y5N8J4?Uac3&aM>DR2WmE1l zC~SY0LLBV-Fz2CmUCC$@M0K5k?}UJYikX2)w2zOyC4;EzPJ#1=mtjb@&PIuUFbdpD z$P$#Tt7pjP{A$xEez7`~K=>&Sa^4y~v$Bga5?C5G`}q<#+N(FiFIx)55Kms4|esZ)%P2 z>DxckG@g^2#l$-V#QPEd11z|E=$PtzWSHDwuF=WTvq91=@CEwV5#^ZwtQ^3CFlI=E z({+lD`3Z&}{;Vg4FlKFjV6f?ba3zotgwRjvbTDJ%ko*G?0albOZs5SHY*tq}g`MT` z`lB+_e{zQ2zgW~QkAL=s@V}f6#S`KG^GqNAB2~H8|H=pMhdWE#z6Sg9D@b4&|F(Yn zhx`-<{y64$%8PCfxf?!$gTM0Pbf$lv_z!D4gnlO!5Lg!4%B&y9uMq4W`iBH?(ked>`LK!L~uNw42% zX%fh?UgJ@~x$u1~#$V+jbKf-3)V*u}7YSSvny!EMMO9o*eVf)-k|BE|d9lSY|L);w z-#0(@euWn(9-l_k4aZ@QY!S`Ij=Z{5@IBa={9#ZFmak%UF8E32SDagP?N^a5#2>vP zx7=ciG-;a4%%t3E4vG#|j_<#$;b0eKG-?hOwmpJu8zpwup9XPc)}9NZc!%xsrkXS> z#>UL96SAq7-5QPX9A36Psw$lIaitr=aR+;L;EL;BS6>=+oi4MK9ldI*?RwfO3hu^L zW-d%L*p(jl4AyzGjt{7LYvUeEhhu4Z*bRW2Dqe=AHft=7HwTR*SFuo!KvU2_6z)|J(!$ z9i#Kcsp!&4iY`PEtFuxG9zd&U4!zkl$-_>jtz7)5l|lWIjJ z#ef{=2vs-~td9m^=Y_R`00TGCm@S%dbC$b$Xa!ihofM@*ZPJ0SvK8;7O&|19o2Sh< zoBwUg6{MO~EB{#Cq-!yTQVAEB(fBxEslO4rgfcE)j|^4zHLWf3SU9HWPO4%ovUsP+ zf?)1DiBJpdt~0rl>s5wt5OK>xVn@6t^J_Hh<7Y7h^~ki;vP`y{)=_zCp__$Zk(~xe zf}O;t;|Mmr=r+!;2{&GmiJUEru}2y&gk~v@G(EDJzg80f+Y^;~@e+3loScg{iMHUH zyc>ecO0nooT;hfknnjUELMhDcoC}+##)KOJRvm^U3zi-SK3q+5cA&t6pz+@Ab2}wC z6Zty!^?GXgdBAe%H_tIIU-P`U#UaI;49DfTqWVML_YJ-~1xdhlXMrzm##D5ce`FIY z*UH}qmhhGyG9c*UakM?h)JI*^$OpXesWO-2-`~C+f9qM zv)yY|8KWC~jUhP{K11`>*2_h7hvN(vnFb&$Jv<#Mb8UHr9{Zy2ob){5cF} zLzYTrQvlMcXwDIfuRu>ER^4={B8D7$jJR|V_o7z3d?cqhNJuO?CgJk91tQ4m4!x=w3(~8W!YU;<^4Z$L9j*w00)wB{`HjrHW_;PZq$T?`;?W>zYyr z5iZ#mFTWNiJ(VPlZi6es8f+yL?S(p0;Xh`tKO*!mU|Lq zO}9slo39T%;+sSdp0QNb)KFgdxew_v3E6f`e)iS=&H)bR;k-hfzGXTe`A}i=qV#Q; z`Ih#Z!WdaEk{}>a!9}7cQnA0npRLCyEuJ~8*y&<9rFPs4?62UvtU8r=k2(C!jY#?4 zS*Xwm3*cRd#x*Ih^xaE7VnKmOsz4TNLKS%hXsauh3@fE$aN_FM&Ax)sW?})^EnHw{ z3i2)>_Vj;j`l$EPeV2>BXp7Z(G%zq3k16)_r5dVGNT2fxfg~LX56d%BYkWw(bg|fN z(l4vs($2`3Jh18*_P)+)bJ#(8dto^3X_3Z4a4XrTR2}IcdAER69*a~obgbZ#n3Vcc z)!YVF{hm-p)n&ij;*;y!8WizzbtfpOv&e2eRZmhqEaTg@85u&4yexD|1yvlvuOR4* zu)O)=XJS9;X91B%*(()s)F0E}ozLr0l;!j7MoYyWEyUB+;Q&1{I%{`cfb>mWvDI)e zVC@&+)}{Arov2+pMFTax9uqb~5fQ@msE6c$KkjyR?Vz>x;4#>B>;F9dfWi&|wvL7_ zxUJAy9M{}DCrKa`HBWC{tH83Gy-IkW0$Y=th$qjJXn^+eoqPFQ_cozExdR|i)6#qI z2GsDrCmAT_G|pdk$R~hhuIr8-4+I_SUU_~3|5#`5a+)?P*FB4=pTeFP;NDnsq7>k%XA}V~4MZ3bIFSn^wk1nEG;mOni z3Fw5;UipO9eX2T(z$*NnsPyMr!R(?|#h5eA zz(JD)yc6m%t&{LhWiqko=zdWQWlV12<)Vu^kv$9d}9M z&D=Mv9I9cm30R#pJe; zu47w3?k%ql>_k|<2mNlmc1}C1XV1f(re6-ZcHfZqBK%jt>CRf**)`wR6IFhpMXCrg z6S^7(=ZSa&Tb9s2MFT%-mZVe4AFUT_ILu0+HLpAEBAHKjtV7qDLT>dYoPQS$F4~pV zkHZNyF&-j5-tZF%Ih&%{b_IfzvGh~P)r!D^XcD$c9!)DBE5?wE+3KisozOMZcz+I2NR0#`Kng^mSnGla3@)8>Ge+|y64Q_ ztKf34SUTNwc>ZQRX%T`cTFJM<8)AWvvXLnyxu_Ge3UFP7L6E}nUFGfrz#MTt9Ftn@!*L&xQW3QrxxJD|X7%Wu=j4h2~f6f~6a zx%nSd1bz3-XeAp_(O2nEdv1VfjL3>x_4ha;seH?4Xwk zMlzt>q~VJ4)a+4iPuqJ(g_2#ypV@l(a)x8ktrV~Cc7dLtVM41-$|#Qkg+PjM$-H6C z1pwTuUSSy$O2+!#l#-9OMJzv<8!_59KH}`}%#-6*1T_kJN@o_MCs)PiBb%Lflc#EO z;A!3|4JZoN`UWyCXy`7_+{OGXg??|G+>1dJpFq`?mGh57Fa?)_VEBmc3)p!Sb%hS;UQ}mFEFTC}QnDpzNK&oJ{f;h@Q7)<8qmG z1mF69yae5NRW}P6l&Lq`TkG>7~%vUF(9m&@^x=LA!l>2JWCEcp!JmR^v z5~FGZGpoRdmnuUQn!d@7tc$`*`^uBtiJ7>J^K(=m*rzL+1%8U~@unrMM0(vW90JGd zbFZn7{zf^&R(X+V+?&}9e8!Wd1glXiZA8ZNaX33JtPbi+?qQ}uN{(wRDRUxjns=(< z<2X8r6C%kUNSu(<;hIU-E0IlP{6ei#MNU4^y&TvqVc+GKF14B;MFLlT6A5<5sKa9lP{-#N<28H}?_D zAwE8VD^Lq_P$&$ZL{bEZgIA|e-8;h#C>&41@SJB>V(P_EEKdhP3FaH>AXD9^+wq8( zmiFyi97r^;O_UHawb>=sKBhA<4_LL5_lri7^vlc*$W$Z|m{!ukYiTy)2p1dr z55fr{(!D3GLo;#|8%2k=+C(&>vd%!QT0^apj;z;5I9^1@MJz`pEv|&qbh2Bhp0ACC zQE%Drc;r9|X~uY!=|k{QbBotUzwV4fAjMI?oLq*!>0%Pq{I2nod-(;TYhbA|@W$#b zl9w8rOktRQDH>sR;P^aN*0mF)GH2{k0AGtI>JUAAonM1@VZaHQCXA(Ju`F{cEm6NA2B^T!X8D)p2W-sC)me8c5$y(E(kRmJO&K zKi*H0 zU(%40cH3DAqvE(NC~|tg3@Cf|yR%-s2TM#gUb2Xa5wOfqmHtp;|7WMo|9(Yb2aKvwbrFtQ3Rn?jy~0~YZT%Rly_j)Cr^?|8vVIJR z<%)+{i?&))b@n=Fx}mo3YRAw_;$;PyY`^M`6JF3$|2%~9cM+6fwIBEirz;}{j?RSv zp5mb?6^kN^~*!^dNv9n_a zDE3%~?ZK|5mKI)PmCpRlipI5EA%EBsCx8Vl07`_4!2QS)qk%}wfwV>7s;4AiLRJJg z&j7{lMYEw;ic$vem#3Aac5kqCYPHoO46Vv%QgGUZfJh~QBqlx>r=az(e^Hawp^4nO4Yf+S3Ij` zHJ{20;w%pPiLXl)8jV1ha*fCUq~5h%eg={$KROWPgj?d4rX<%C+Dg@L#P%E zzY`&n!dbC}&^d^!ObQo8N|-oxSH7!+8k(<#y}zHy_xX8EeW$4t=Fa6e?)!7Rky300 zuW)T#2u?@oECvfpZz^A^2gSqF(=bNy`6pl|^kcjq??68RFc_K$Ch{^m#}zhZ@>4v)9jS(EZFJ7- zC*`n)Rx14mdfENj-F)OslG2K;AjM&oc9tD#rPLZMI;n4kE!;DOI0T$er1kfb7MImC zzu%Yq6jarj6IRHdzlqz#m*{G?Eg;|bY|fk8skjR{{4m_mVajzXhEuiMaD~GkE09$r zOO=&{G(b*qp>ds$$elD%>{!D!>%M3&o!~6Mo>`rB3l0J$3bgE?6UGZH8(P$FQ_s(1 zql;0bLTlEkxjfm>?)|dfBP*%p*w8bNP>u88+e)bxd}-mh9wdCpI{GmpQjzBI6wQWN zp|CaXQ9j3jlqM=eT8O*UC{bExZf1Dl?k14 z`VX6>&&7?wtPvS?PQslI4hOSk1HVuj;1IDt0gg2J!1eH@dI&4P4J&0hkwM0v4N|?{ zVpdb~Piq^X1I1ddAgfNbVV`Sk$gw%lFXYOU(jcYYTs-id_vGX0NNz&Xy!mkQ`P<_K z5->|!m=C+RtB%d4{O)C2rlRh^r0BF| zjy2mEGltVByvESmkwiUtGI8jOn=lNtc*a$au&^)^lqmS&bhn&94fUU?WtzQHw=wZb z(B%_lZHTud2$~?qR-~MFkhA^A-_*co)f^wbW*2xIDL@ShRw0Y=6Kh!5Yj$$zf~FzE7=(%CZqh6nNes`s1)03UBiX%(k8p zY2LZTCVL}4x3XCsRzLSU*{x%Ums*;l<(9hn$YfXuOZP!(0Y-I0&1|{^y|vxIr4Nva zs8#6UzrN|z-1#4C=TR#4ueh0C1zI{=zgdQ{oXO$CDAk15478XvB`GnkmJkLRB(xL(w#*Y4V{a8`GKzkecmx~OuZ@2#s$V%v8e-?s41 zqT}YnX_7^C%q^aL_|7vQ#+*`HDA=_{rV~aw!v+G0*ExabF{9|K7N(Y~2}a582x($_ zR6eq3Bo15y_5sF9L(5dy21Ff8ag^9|ZU|1@*JaVQOb6LiqdSg*+i`DmuJwNhT*LGz z{+|IKSu)Ls*Nl^wzJ2XGWZT&T?R%?xy1%aMV(tpnr8-y%x;?TP3n{NhGCYW7xzgCy zJ5_M-(o<7FGffdem*ap;%}4w2&|mZ15Td3^zb6Xzcfx)~xEAh2hn(|Y zyvI5Lj18sVzkIFpxOY%Vb9mXC%yUzQ%R%t2KmNreO7R>pp2)z=98Hx9wEp=04A>6X zc>@x%ZBKYjKx1ceN*@sNX`^Pokl$g~>yu2P?#A))GKy3sXC!~>_RERPvnSKF3vgce z&a4*&U0x@Ls(#_{uvsfRG~?L=Yd$iHfvpKty23MWuG5R z=_xKXkAMKFzTWONQ#$Lu)a=NVK(i%}gj@a@f{x1-t3dK&R1aLj)@CTsvLJ-5E2=;? z1#{wu(}gGzq3-_lxM|cMFbfg%#1d^YNR^hs0vH_fF!J{8zrkF zn+u>B46ln-l{-js0`(mdj!^-@p=zlrjZkmaUK^M?!IJf2nWW=XO#?Ca>!nT&r`la6 zfofMC5zNs_DWQ%@-zV+&&65*j{Ta*DeXW%E@@8{|H~1t5Zz1SDV%s0!NvF`VUx)II zJ_cIM#Zf-(m&Dm7m&kEe*@6@e18OGO=kpa~M?uj}^7j%EE!_#qlk zVxBS1a5O6x*v$}c1IPSS8-KF^hW<$ZE|GtWk|+U2_t{ORQ7*eLh85{}A@@_gHWS5+ z=KFZcqsJiit8nsDcZj+|qvEbZ^P7X|x&~`_>tdooLglS2Z(F_9$SL;{{+Y^?57H_X zt}MgbKQ`bQhpGV%(f#<%ABCqeH3#m(>T?*R%TQ zWYI|+fRidJ^nFJ(<)rr%9Fd0*65h)n(*=JEf1kiJVei-X^dOVLGtqbrTOfaIVb3QFjS*EIgfu^O zwt3MIEWx!i);2idr7VF~#c^UKEEKF$Cc1|A1+{A12ybG}_D9*HPREJ7*Gs**%2$HKHR4;ale6GaT@ z(w)iyMXOWn9v^D_0N*f$f>rSJ%uhnAEYfFBup3cW=&H5|m`8!QBDzzmpTE%CQ>&WOpD)5JrN1tJ* zCJFzWb;B+Ok+Gs9nmMKij;N#wjqtz@nXF0veO7mJPw@`Czh>K_Y^Y(Kes<+)}%b|(TPj&By!>KrKeWCrAQfbdd zqMcpcAM{#_P+zYC59!rs@wsD$oeqYUwangH?wKYKR!94;+zN6lBoluWwv2^JPUhn0;}0oy&pM(0)0_%Vs);za|e4fnd-L9HOR~ zpSodD%25RFe5_HcHqHz1b@CCvAdj@%mRq*YOiwk znN;jV>N$h8E(a#=K2SnqqENpmdZ0i|wPhN(wfW-clxsUuXe)uh*PU-)!j|)rj!A!) z95Gd(xnQcC<`BGm^2 zdorZKV@hll<<&EqK0EzzBiaYZ+8L$OI4WpmD|H$p;UrNzgAsAMh2KMy!b_kQoy+=W zLkZyxxPif!Zd|ar?5*kX4J5c-bkmbBSM!B%kck%_P^iLnpGUrs1k8Q!Q?h!ispBW1 zXne<)&;}-18-ko`sEDH>wL%WG!u^!Sc~jVyirSs+Gp0*al|y2EaXJXneh{eN@FoPM z{qhW&4><<>J#iyvf3+APv=P-Ube1!enR}|D7NpO|Hw+x*8p^b&ESwyx4jH}qw)%nT zh+T^_$f5#QT#SOZwe0GcY+a3`(YcOR8TryFUyc}>p2UCxc2(qNLWf4BTH<~8Eio=e zF(?gJKEu|cr_rzl>v-S6a$!>2+P5^3$N^3 zaFmF3>ZH+#Lb@QW{!Z}Gx7mKOHQslSn0zJi&qsYkDyH6#nQ|0quXJrpHNo+qPGPUo zE;w81(_;nI+#yfen=#Lxd-n~rL}6Se_N-UldYxxFs-)n%+0knk8@T4W4gSR18KavS zzlu%O5imau1Vq_tX`E`kK9|XGZGJf z%lJ$><1xf6^#ttO82ERn`v9Wm^PW<`N%_DS2Bd!0#8OMr%+p#4)HrYgu!LQwz}(4i zrRS{RpM67Ms!J8J1XKanNe8C{ehbASxdUVf>ut%AC*HZGIx(vwfg58#>Htqy&H{M? z3145T%v%?IB&PVsN3Da?up9!0rBK1MA3VXz1#Z$p9SR)v`^%HRHtj(_zMD>2K6d#5 z=X#6)*&W&j=@kVL3JSfe(f+a&Ej1*jFvQ`!evcE7G+JP^-qG_{(Q1H5JZ);` zXA>D(s6~SA@de<=>B)WO8?$D*+IG$0Nr>>2^^q{32Se-cjd?E+isfQn2j8wL&N)f!f@ws{(i5L4UO#;}5{J5W@&z0Ni37n*^Wh z_kDc|^ok+7Th%9Os0{Wi{|e;D0942j)tJp1yYNe4k^ae>!JR2j#&Fl0&d2q7OASgO zrJu={dcQ^};L)_H$sn1%Htg8y>*d92u2OfzFgxc+Du7|st#zTc1wH2 zb6D`W?i}+HR2gQoS1s9H$E*Y0q#gLG<%|eUnUe2b_`zYeR$m>HV|;)>$nl{_9lZ4? zHE=@e_1v_u_vpf1o7$xW>mfxvr%oB`4sQCNjS54$dOi+8cqXSLVx+;@XO`hS`kB9s z6>PWVK!7hgVVQ`?VO_#gWk3So%)4>4BAD}ZCgk_Qi8`yn1BIm2pOxTU)r85ryAe^N zN>K=Vy43-NG<~+E;|WO}YG(4VKrbf!$%dKtgXJ_7giMQ~SSV!$Jv0LtGVw(lzwfW} zb}AUvZ&s+yY!0NA+~;N!Q(NHJs7=1Y33ci{>(!GY>vo~EKS$u!vKD|7WMrL#6)&)G zjd9;}XKzDOfc)7H96MigP_Y~XJW_z^ z^jHSmERU;ESMA$n)GAcN;sxoXcDieq$%#nJHsUw7bmbg-=jWs53wj3@i98^*7Wcy$^d2`*nw{R;`T0PO`1jfRaaK1zhTb_&x$kp`6YCO5|G zGhFRU-GCMpWTLvHpJXM*&tPPNVZZhlK7(Uz{2etI09x9t(j!B1iHYD>FCA&ELUQ99*9iP^KnJICdA&t_`YSRWONJYZ#BVTkc3_;Pbq197?oTO0 z-Oy)63Dpl6cu8m`xPYHuZ=#s%>$Ph#Yp|FKAjjbYo;%Zom)n6T`vR5hO1h5N%JQcB zZE#k+i0qrz>eS}`%GlqMY1*i`@Z&(qXsZWu^Bc@KrIkpha7d2fh{_3CTMHv-qDH)o zqNBPfQ8+ci9>{QI-SxN054AT!VNPjWefO=k?|4*tcYjZ@{JKJ+_uDDEh=$=Nw^c~m zNfF63w51L6DX36^E{FK&fL!hi-a3sNy_4wH<{h|w@^BLe)zBMb zzuQ4cRHG{!u|9XEKR$n;Bz@!Ae9&N zLQ<4TN=<@@GXBBU*6-5ID(SAti+4**JY>N-aR3YwW}9-CTs%|&rvtc) zJ%bMH0U01k=XU#P^;XbONZ*^V%9$bZq7)Ld8)22yG$nJFnh8(1>GvuFn^v5H&C}%R z0+h^K1Za4i^b;yDkK0vvZXhcP?h=m611?zdn;_|~um{oc%oQuV=EKX8?^!2C8<|;G zxd`SpD@S@>{c%I_Cwh@r1i@Ghb~PY}`73bfKKmpI0gJ;GFHYo{HlV@kfujzP%(mF! zk68l^5mF`kGv&@Vx&`mFE0Z>wa|9GW&+9s{RXG0~s(=(Mt46~g0 zL)SD7`S#xBpTg7gFlkKXDNRX75vE}hyWi_mw7U04{mi3R))*cIflrHLC5qVNcq4Eow6&|HEP6=faS=P}Tm z9%1Hks?wf~?GJ%c6iTt&=f=kp>nI|@OgKTHt4{6S<#FTs7hl+QsqL7lchac2F{lK7 zPCy3$E%M)npyww-wjx7hEaF z1|!&q#5>`Ij{&s+l~B{MmjKsEU7!JAyGu2&gwg1(qa9sHzJBB`7dDgWR6eR75WRGk zFn37hc^2M(j>IZ0MTs;%rE~qVzbwyKHCWB0`GC9u&b7-3M1fabGD-^Y93<3`hP;i6 z96V*{Weg|=bUqGNdHXhe9OhiszG8T8bn0<|B>TLfe9GEFGz!hq@p+==nC0PZ(-DDo zn56|qB#~rqc^hI>-VH6p=u4Z=Cz=`0W#OP!ozeTdMpgY_!(q`bADr)UWCiEYBFLFl zJ26BWsC3p`;F!bE!-q;1pYBh`7ZN522ER&oYO~SI3FCKLc%ugVZXw(}j`9PkXK9O9$9v~D0)3?F4OUSF zLofWp8IL4xZW&+jdds^5Uw00Fre0TzEf^{HX8HuCYr0*~=eD|GotOG+pVDod5X#@p z+7F^?ZA;q#=<=n+a?zo=mfk>W*=vmvp9kBcl|hW3ux}Td$WkWFP^WlJi@w)uE&;L! zH#YVYK1ti%NtcMqGF7)v%s3<99Xn+@>(kK@mJMAg1+THt&k7A0<3QNTkA@s_Neq%p zX70+>)5(p|^8WIvb4tL7IMe(X^?NFZb?JBIVvNRrsy$a(O1&KPW&o+up2@hqtCr>m z)uQ6M_ciAg_lm?}eoEB=Laz4BoHZsrsK;Iv8L|URkPD+h>hu z*&gisGfgYrjrcZ@8#xevR1jM4Fn`+-N<6SaeRe(j;inlm3tR1wZ74#Z{^Cyz%~X_1 zc8WdL7_8nY+(g7zL#$mOPc*>Y$YaBNyQjR@d@O-kz40?4uh(ce{+8x?g%)fhG&&4A zdaG~yU^PzPO&CXd15Ll(JDffa(9m|fYH;;y8rgB<+=NWCIJXXlBFb+_gI~NqUg$Br zef)I2t(4sC&02wArVvhM8V%mVMp$0KR?x03!9$>1s1!8E^uE<*xUSoMX3#E1kg)4l6_PC^=7>>;Dpg2osa0|KG)^FsBmTN%hlYEnN| z;+6Wsiz-@-YNktMxb~d#!U}El*X&V3Dh3sk?k>J&JNq+t0LlQh;hZ0L@LlUQgTMJu z0CvMZ487&^BBaz3iQoQY+3(D$?}J$5(qN615gj`SrJ3DLzaY_RYFy8f>rRvmV>hvF~A> z6C>Y1Y|ZX$xSw6O(ryJ<3m!cfT~0@(A(tZmRu5z@KT=$)!R?aKzD5YI7Zm;N@|uGc zR453qso4tob{j3MxRDC853gR)QDNWOmJiEjBH&qri>9T^`R&kB<#fP;EuwQdPe{7j z!@IN>J}uNeJVmMImXpPJUaO?24~5)V66}+?)=fq zz(ZGt{re1D6c18Od3(!f@N48(w~qgRe#%{hS!Z#4G!feXLzp~pZtiE6U=}Iqw#oc^ z$Y=5uT%DIp(JpUU%(!cEanWAFun3Q8wd2@#4`4oEU`SBOP5+}G`fE*nhfG|Y+W*qf zYcQBIlKw$kAEin-x&1`#qn^pxuh(Lo356MlN!ux>6#UzC=Q6K^?~_kWXhmKdYETsw zly@}?hts${3M(EZmut|lL`}kZOqw0R&^I`a`#j$SJwy9-J+5aZmMh*xpVTTKT@Qsi z5V$LTpV}$**ag8Zr*&63u45k@eSCVtz;(Z&6KTU)^~U||*D4eWkc(P#!-p|v1DC%N65^k*?0lLh+5)B#$I2D?pR zTK#rDKs*ywV=^KxlSr>*eSf*T+kW+IS?$cw(AnD}dwh7MKFlSZa)2{!+^O|F%9Ss2 z?fWf%!N^@m&2EPb2A-z`LX=y`(2qF9rZaTujHQIBf;5E$+KTMW9w-_PXCmtrY&ja` z(L4fP!x^J+EPZhz&kx+VrdXP4j$BKsPb!{q_{oNr}ENLN@0kN|AM2 zo~B@^h8Ze-Cah3g+MM<4{4;&H*h*8Ri^~!zh_pkcv+9Nq*LKiZP-$7Va$!xJvKZg} zkQ;J0$gB}P;h~3c{#CH?R?RGbh)wfhHyFnmR%_rh8BGY@->&^hk_JhMXImgERllBA zK1YJ)Q16>Ztv6?-S~5=@zV*}Uc-|`4BcUKvrmH(%04#s7fs?z$V*_6RC>t+he=mk7 z=6L-KTlY~Rz3zKux%;P+8_+}y=8 z9(^wW%{8EqJU?XA+#+$hU~hM}e14wat~`*!abS!A`P0kSgPhZq3o;bGnI7#vXXYdo zj2kouz!i))Bx^Hp4_~tfiAtS%`iSW`4V)JGF|M^{aKxouY3r#eOKuhAUOyLu&nTqh z>`9PT@H%3(7M;6P+Z~_U?hC8`TLANVf7tB$ZKYwHtP<&)nVtdM#f7;n5h1T+Toe(h z?db#C%jkdwuS^f+WNb)D(9N|mgK9uW+Jn1sMPHFJ@a+VA)IG= zT*&YshM&*NX>})Ou5EbttOk+GEybY%`x6e9iy_bPx+$PaqTUx6Zgl4=08NCA(=<89 zf6aU%n4LPFKyx3drH$JKkc)XSul^!_eav)?HRQ6Lj1AKPKKXs;ZJ0gUum#K>>{~kpEn)BJU z{GlRG*evHTXB#}+xL)NVA=US0K|zn1F!W4V_q=wt*a)h4jhyPofCy7@!Z-1vS`dl& zwUY-}&-pq3mjY4P;eAyYr_GQbdV}S+R8btq-GWIh<4OcpyiVsXTqkOUKw8XPr5x>R zt#sPOawN6Ax$!^BoiSw-H^|UqOdCu3hY|hRKwLTf!Dar&e&r?E6!W{#JN+ALy>Og9 zy!9l|y58D%_Jhb7fFaLteLQZps?bw{&fzm_Vvoc`*5SP@~Ny!nCG6n2>rvpSEbe4*I&*A`==e0{~t6c zDEL_)ZO1+5MKdeb@6}TIsT0PRH9(Oy~>T-6~p#LAn-YTlDZA%;7!7aGEBoHiEAh^4` zy95ux-C1aY1oz#r zm8n!)9g5^AOob(hPuXdiTGoSB)9v9&eD z_w4sD0VaL=tU4aPW0>jXZ3$P^w-xL#Y;aonI1s8EKmKM~8D?f>QL7Xx|K8c@eE?z_ zUtw$sjEAjUwk;1jEK#X1Ie)2-p;c<7JTjlhl=`wK)I@yUs)62jhyFndM|QTqV^A;&!B%FHSRA+O{D zppJE;Mt11;?{(C0Zw5c;`{s*L^gH__f?k*-ZzxE@^~Vu5RP^V#Fz>D9N@xF%8BZ2s zCRk}zen7dPK<>A}3WIW0+NX6nxm1r%Y3qTzx0It>@FW4H3q~6v6ef018HI*7&vBj$ znQHJTAamNZbB5BG&=mic%o z-YUqf@*k?kcc4SnfaIOdL(8(m(6{kvAB|XbET>eY{@as8RsMEgh}ED%<@d{$9YY%# zD@gA6Z-gD+rD{CXDoyf=L1792y*ALRk%=gOZnDQDHZQ1^W z$D)vv2Kn}Aal-_P&ZSL;1D)bYu2AkI{Rc{_*ZinJ20+w6MiinL@fM@jNmfcXGIebo zTe$gf^{9F%!}|L$KE`|a+rIl_iknq?P1byoR%F(vKP(w;cBm`3zNzq(Lm7QFU-=q5>eo zlv!|1p5*o6R|(Cwh59OZXriwr=&m*i0&PDdtogNC_ezg#p1@ppceA-N<1*8no+JOqJF2p9`Eu!gcQF9&}|Ml9#YGU(JEd*7fD;QJFl(S#}rAs)-Iy|xFnBFeA`f> zD7pE5#M@CK?N@GOkJJEERsUhmp~196I9II2xGbl$k6S6lY^E5R^_tGK;rhwMK_k`c z65-$>^(gZ5oMxY9GQy{KrUQzZfrap}-jj+ZMmp0lM@E>KKG+IeYBC0Nm+ zQ`TLsIWR}9eI0B8R3&1$^vpZ7AC`qw3Qn%G9_ggw-_yF=EF7TlpLQJhpn~LgooBf| z63(NS7keU?%Wyl2x}_6HoQyl3!{JF*U0YYP-Noge*4f&`Nv78Ya_n?yN5Js5?fqML#B?xQ zmIoxbOaP%r93Ci{3OSOo*&z?=0Q<-j2)i=_RuWCtQlEg5eQgC)_YA-uNQs~Suf;Xr zD@S_F(OTbX-HL4+0%!r15xukctDvPy`YDAR+0x z3Wddnq6I-f$GtOuLwozpa^c=p4J!ab6Qdl00)ISC4xIn6-iopzpw-ua44?~e#s9Iv z6SPAw2L~Ny)G6i~Mo(ck z#+BQ@#8WT#E9s4EPKiL|s9bwR*>%Og{nos6K#98GYPX~|E$B`8)yxk#vLA%C%E9!k zY@iB0H2{4lznhCLxB=6=sCH?Sukf~DbztZIILM!r$%fFJ%a|ERZj)t{#ACIGK% z(6<>|b}oe7z2E{6T33O!0P_0J6%prEEoC zHcyKgq@EpKZE9We2uGi5Nx6>YrK}J!t&8~+|dD5w4|&XYbJOuMv=`=rme%6A?J z!#~axeO*}hrtpXn6G2&?-!^bbs`PgZ{fDsuHVIk5p(%r|Bvy%bU_>5*&cnIW><=#W zu?aV04a7bFo%dZw+ZSqzjnlpEPTvp|zIEiSS%1}!%s%glsDs|J@yI0~m zY&O?G8#9>&q+VY@W2v)*;)~uE^~PMMii+^`Dur)(&Lc{#!q}PR=uP>8iMg|9VwV^b zHBaK2EkQo9^?z%GQW9SA^|C+!U(W$(8+Jthf^O{`UAXV=@0sBk0!{-^6c$@pqXvey zx3;>;vAYU^+$(t?7^r`GI@UUWlF~8Aj1{mM3n~+daQcbdR63HOY@Vh#c5+sP9JqAn z6RP?cCF=SWE839?0z!3XgkTPl_MLIOCJCmoPFoV240bu@_3^UUWM0rWa+eeaLAdlV zB~_?mpU8WQd{Tbd;$f=AC_R4b&_^-Z-O~TvM2Jj*D)t(ua+OxjkRov;hh(62(N3IS zJM+P+aT6mnMAd~`H`g!D*$I5Tg1{wJ)C$J+5+(=2tuoE%h0@C%czSL+MvZRUQ?O4R zb_X(1Gd52stSac^i+Gvb2pRM%!cX1XRP2)zaQP-!;EL)g=Ngu2a6^6c)~Lj{c8zG7 zYeNhsBOCyshi3yHRq*V1V^?rAs`0B|0QOi{#ZE#Ck){5XNU-xl*GcY^hWVx=K(2rCo3R%a276x?G53xu{^v~~%zkJj!vzO_cF}?jdo;BiFU>NT!e%y^vGZFo zb~j{^U>mnZlPAV(J0#+Y`T!FgLl=`woef(`L@Xki2Wx1k9)Ct>OEbW}d6)yH)sWcF z4dZP}akMHxi;JDt6)omp`az^~1u`oX+L!L09%gkS)HIndwLpGwC2R~t$6dLFK9D3w zw@^o^$YBTx2P^Cihu6gmMJFN-`3GkvY{BNa@8{yKC`Zre?G1C`J6wNIF6hVF=QS$p zw!L?PDkJekrUMaiU03?#4BM+@_bDc^m=>+8%hwijA!-P=ResbLStgNfS^0?hyhyqt zc1t?4{A~5fns+kI?|Sf+qVd1m2GKqs4`o!pbCrU2azTXZJyi{C{b zOgBmgl^c&$jyjWfxI=vtyrOf17)08!DU~dSxDmw@LG5DE9wtR5)5Ct9P`L2&F^HO6 zZ1yzzOjrA~$L`%Hv~^!-9b)+(8FuLIV)v7EXL`SYC(S9+=2sqY_S31m4S;5+jSorH z5?QC(59oGU1uLC$TJ$b)=wz>!&l)o10#Lp%wEP5>?fYltI~2(T8m+t=3c!$S#m1&x zHy4;&Snxrn%ch%aiHx%*urVO9yT!PUQSR`;0;VV^_4!ey(w~>9kaZ^8DT-ni&z=0uO$o&sIYvBG5JWFJgH3Go1NgZBgy3Opy3g#FPs;Qig>gxV^D)*Or=0JFiQ&~55 zySJBkQELK&+|Wy+cq$diufw}z#X0{o%quIT>62$BRw9=;wRhiLnB&l*9e3K|e4tVB z^WjtpJ|AZz;bF`3x5-`)Byqd405EH?=WX>B#}wPPIhppLKC(cLA$7LM;18)*61boYOCTY*AJ_ZLb40A?C@!F5jl z3(QKxz$n5V`;Ph16Xl71%5sjNWy|X{zZ7BN|nE5{_*Q~BCiItU;a1?L_(0dYmQ4hG( ziFr_hqJ`7!n+Fl9-d|Mz@(y5bc{=7nHXw8)UbpYrM0(hMi18At76EIZRv48k?{pZd$vzJ`Jbfu zH5D#3>Cl=F_moq{ICn>k>K@F4r@~WPu>Q3C1C#@ZlGQI^-ylLD z3!km5!{(d_iy|`N1H2DtXAfwih%9}^%pkI@@Yjd1)%x#K5(aCo_Ko(XjJWHcp z=jng|@EKe4hFGAluwvdnLWC!vRFPL{*ZL(fg+xkVl?|0FAcX6WgmkbU9swb<1B6(` zAH!;?8pg=Ol8oJK_UY>qyBDJ+9-mi}j#m6fojG@eG&S)#VnV{=+7L^#seH6Bvs0vl z)z;Q^&SIS~`lhns%*#X)#o8usCGhu%56)41=FjV~+#ENJtf|YhSM_@3&-c(}I4o}l zr&(hz46#0xCv+E)2|?5oL#T$J52{xW#M@L%^(f*qkw;qhE429?G{(`#a}7eoRUhpf zb!7Or_bw_RJ$}cV=c3tf;RpRG_2bgAE4-aKr}mH{zSsCDM#@N8>s> z2-=5oMHui6Ac{yZ4D2jNRdYt=uRDD5aDKkOS@qPk##k2s;JirC^bqQPRx7gQtfAFIO@PR+%lR~ofM;KzI7-$$#&=47P!&hqzSu!ezrV>SMO7aKx5JiwOHDiMRy_|5FwJ?#1Y@eo{a72qcjB?^tLcgEoQ!iNX2-%+6;h;VRM|_Wjn~l75Rm?vg~h^Pq3Y%j zC&w<0t&aX*GU?YQCFa_&@o?in8hwT@PWPHW3Bf)V3`x1Qy9Vhldu7L37~|!x%m`CX z8LalEaWq&11sZXr4V($TYVcP^}KQZ1J`! z_stOFtD{Ll3g+m{Xe~dmzHYnF=O5Czo|sxJe)9$qQCWXli4O48`EMv30%{KCXi*nb z|09`X$Dk^lIzt5W-vaKnm|d#%KOmM%^#AjEuwHfFYdH-r{>{HM>v?W|+eK1V#VfQ( zP9JR-OML)1YI+Q)?_mBBh5!Hf&cETcwJUj>4QbtZOxKdj+OA*iLH1^2Er@|`zNAe5 zfH@F(!hG<~n4Vk^?a2YpUb1kP|9fcy0JX2mcHcFXKRWx70w_8$fRgkFc=L;Yg$v=C zC?{UgLTCVE{3Bs^QAsg4%r0>^f~HdtMiaq&{S|{l4aj<>f|;WJsCwbvNd1Rk9L0YdI4r{s!sT)UZ1D4emL`tiB-e7<$&Ubs6?$B*V8LWVG1^65quI5lV?8+Y3V%;Y%M#l=Crl( zhjD791CKZ2RlN>vl|Pu?o3=ZCxg5)by>sV2o57!cUv2pye}8#SS-^G{u(Y^iGr2>S zt|UK6dVdwBceM@0z_19+VNX&!s;ODOCNFCh$VoeoWA*GkXk7G z{e)@7N(Ngrz9r^XGS{QQ)b>SJ9Fx{`aiDS zH(W%9lf=VV&c~~}nAChF8X{M!EHzu!3_aeeq* zA>#16O?u_|E>F(PULSEUeOTv8TTDt2UNSr`V1IOYc=&WkGT0EnY_9RBA!hWvb&VJr z(!CxNAUp}i{9(N-c%GZs@3(g3%%i=H?$2hR2Sud^fIN#A#TAPTB0)WIdAI5F%%>x~ znjlaSWHibcd>saM?Jn7Pq1jb$ET5TC=a0+V)n4pg05>s%?U!kzS*d@IC^=WkYJqMt zlo~Ip7uifYWc5Gw+_r`Psprm_8EkTG1TEr+J)f&NW{6JWoI8z{RK&0;eF12Iw2f>m z=W3q$p{hJ}&+k%iq&elTd;EN$+a ztuc`>PB`_FxIVwwH14fg#iE?vsVCW%9i(1$OO}E+0_Huw^nq?`;6eYtk+9ZnPdIT5 z*};?E|59sPk+|Odt=2XZ8a+6cBmIW`XF>od;280*X}5}bvNC$!Ppl`eDGo`eroRA_ zjl_kRN{eZlv?+mI&H8T{nq`3aR`~}pk7mi|Do?=moO`@$ILwa#_X)6}4?{yOe^n5# zXaGLHTLhxi47B=KAEg~me>HikB?U<_sDaB~W!H+%ir(+8UYm*qW0^Pw%Zy{meoA=WOrC4E+k5+ zbmG!()#wIK=uas?ar=uu2JCfI-jamaMKedHD+0R8IBEGYPGVT&LB4!DolQWZ8(TW@ zqYO?GwY5yIwUkdA9CR>VqE$CH)8uLo$So073KY9H=KUu!MG&7?KATSngKtsmiUmOkF`sR8uL--^+E^5+0Z=dMyb=O?Aestg4&xw? zgyEv)xv3NO%UHo?*tDnnR;u%gt$O}Awx!?1YTZjNPu9&?k29O1ahdr2-h>?XwN0&e zC$U`l31L(8G0)427y2zcsD)tQO2IR8w|8e%!@6Q7UBmTU9vK7*cC?GiF>B5_!UlWF zw(t>4jbgrNK3|T?Rr(ZRlIi0ek#fMRQ^Q7h%-@R{!H?!|aLvL7i zX!(hneoLH|Z|a@bvf3|8%zd7RB?-VRi^w}vZ@xlLXullCr4HYUo;y-hI2%?v@TogIWLY9~^r( zVyC!yPDZN5ZNe^F^eb&J%d;EUJ#RDUG_MZCCLRtFe5MPr&VQpmT}IQ@OI(;uD_QNx zy7GNeKE4w{C-xk#uy5RJ17*p~y6)9b_edC1H8DpNYu;Z&~pKtMwQ zNKO&DXKL6L0dn^YFNH`OXSH{d^5^&nF7GdIsSW%z-yq%%3VwF}rfw!IA^yhlCV1Jw zcm@r5^}%5?kFX`rg>9ML&7ZAqPI~p^AuWy9Z6L$f*=AtHV-{K0=*`}IwCSbJkTabz zPlHWj*s!K|IHn~8c0jdGr}KC$k=pLsls^ntD9|Eu`d5qjRs&)@zq?~vxztE*;K}J9 z2{Q7Md$?UJ^{7*`PtGr#f+4Fuy;x>P3yzoR_Z%py2+B8TTebEtqpg&BZLmX#z36hY zdweIL!4&vQv(c%f;B5|IwS*S`o)rk~N~Zt!?}#_cPdFWd7Sub*Tey&uq5w!|ow zYIAVf3L--bd@Tj0jo7jVr99cG7Ekvp_A0wOW`*fOVaJ1k74^ls-bGSPN8K0zr_}udO>8hxALHkj4Zr@CqoL`NX{=|bAeqyF z6rZK%JSAJ3RR3tf5kx5yn&St}!@dJ(ZI9+J=!4)bq!w$F0SgYHrlXS=rW*6A#5x_F zh>3Cou9_gqWQeHExpmjet(^=S)UxA8M}JuHF9yEekI(cZ_Ge2Mb6Q+mzQEXu|LP%< z(DGsVHepxbFTH9rG(;KHU{XSXl%7|`Wm-4;@uVEz*2Om@VT+Qy_W&2c#3D=sDDY5p zTX%sF;~O2I+f%M8R6)zx4YP`W@pcb3Tp8{kCs8@eZ-f>aE$Mjb8D1cJT)8M#0OS9=nm0|z-0>Aho10mDQ(wjrCB=h}wL0UUZ&h{SDULM=5f%7S9(`|a zWR}G8um=q*Yt9ePSgz;5w!dMZ+2FEUOZ&-wN{-*`m{m~fKRpkhV9s;>bV6R}Us2yx z0+wzj)&9TV={us#3jvh3Ze1z3Rev+(UvAbG?2avf#5&GuB~G)^X6*>@P2?Ad2P-$+ zpzb$5eF(zIl}+~*p`LTInjgHYkEB2(RHQ{DxPgonU{qLY z6zq`RQ%GvgW#x~eo3Zat0RYq8F-)_H7TiXyLe`Y5q#x+v(*^B~nv_W?WjGxJUvQE^ zd;cB0D{JKQZPO4huE6k;uMGk=`)O2AbW|T_OLaE;mxIwL{;gr;ayca}OM;f8@;McR zlXGK$wIq*KES^Wbhb%)3Rsoz9e!;xOmv?aZv?w3s6$|mk`tE#dzeTZBw|O8<`!xk? z#aN-dkBmE>6xdcaN^t&YiB_Vm@X6T=2T{{5YhV&GG}A-DGLkmaPdt#jYov0C#W3hS zR-DG!3Ab^2rF^i~Bo}^X?wUsPy}4QUUDKHiL#Pi(zHKDc<<_@N3aV<3WkkTjQOruBY}l;dIQnQiN16eD-!6RL;FLpz8sS zy`Is$hO@I#3AC7SLXHlD0Mrn`-)V4TyxM$HeeV8zkU@igc6&+4EQ4W*G94$FKBxlS zKR4mZw2Z&4gOwWEu>K#d>GWT0QTy@4R&{pExv;AmSWcPoW;x13X4oU!HjHX0P)a`P4(}1>2Yx1OV9j-$K;< zv*P#Kuz%x$sHO2m4jli8QS*E&;?gi@glSj^&ea6E1~l{Grg1sL&(%sKtkv!>LId#Z zwlkfIAn)o09vCWLVL=&d)q9nOTfXZUi#kp05`dP_O{@raHA#YqLUWzQY2{th0q`#D zy=Xmo_|r=KKB549N?|uoXAW4k#aITM+z;g++2#?*rkL+L-dZ&5245zECwy=RwasT1 zq%GzU_6VGhY3C{Sr!@TEes6H_SB1WzsLO9ivGVLllnOK|K6`CDq=35cx%RqgEI$}g z9k~KFT^oJ?#o@VX4`&|l8>j`>5{RjLwUTo_KA{T4b zrS+KBO)(|OPof1y3+NJ5EK5T?;9bMc@8P8igFCy)Gz*5 zyV!>|r=23*82$ z$Ji_O@9_K_d075N_X=#1ao1k5@qiAvf9?C9;O}3NHZkhVm`*sdV<&xJG?cLd%7$&#%ooamng1?)-A3K{HH21 zoHFBHaKpS^=URw;8M0c)L?H`U18sgg?u(*uC2+j93WdeFUJvo@k5MGmH^_uFc)NlJ z80=)a-yENrUU%c@oiZg8Apq7QKg}j7T}cGdpeRk!056~Y@JM^zecAm{ z#Y4O7U3>lMQkj4|ru#ImS*AA!^DLQ?vBBFz0*3Al$M;vaOBx5W7_rnsm)bkg#D6hM z)Fi;GhkCAeY&Ln`o;nUBjLU!-(pqhyDo*}-RVvapUg<(%9`)wJhFtF7tH z^0p=Sh6kF2)PSneGCfMQ%`Y;)80K#CC~V*L1>^&*u-m~od=Q^zyROr@XOte{`Y@BI zg(?5p=fWVv1Mm7h$PwA-4Ll(3;NK)eeF<#wYA_W8}S z^41B?ww!Y)rtWI!%qh{ED4sMkyRHjmzg z{s|sxMAq;}>ZM^UD?lr_<^~L-T^qcfID=03Ne5zS<3YW?i85N35ZILEE{vA#zh}~{ zI|ATisN{&R2lxel6MWvVk6Hut6o>uf_}rb0Odlw;Q$#`7yeG$?P(XnCDcb>vNJJIJY0!b)p1zzntqg{?`Dk7`x??0F zgt_NF#8F=EN8W)GH7JEv_0zC|N}r5MHHw@T=w8P*?f;s2K-k(HCjh`GkuXAWvp8Wy zVdP*^P2NVrE;hLLZ~}|Fvyrvd$9?dN-cMVIoUT@AM8T<6<0Clf)?3qXSZ9cU29P&s zFRou0m$4-olub}qM9aH6cp?3!LI_KLSN4Iz2fYggNGeQ^WL@I;D1W?;pH3@Vn=-R%+5LTT zNWN)6iU4qx7=XiOOL$8lFG{7;2Z58)c>r+K2Lxa2B^}UT!lM<-D+IJrwGa1V^E^!U zTOXRVyX6Nb_g9M5Dm)BrOOw@P2cw5@-W zW(JRF>I=zETSsVMgCyU!Jr+}uWO^$w!ArY&j%Lr^h`V|Ad=v5ARL213O#|-ioRuyf zI{jBpnMe^Gbi7n=$!|9?pbGQLPrNXs)w%z$Y6a90 zIKzl;A(3Dc80C&i-TC*?U(RkCrWHqLC(`-X4%s{BB;3C|uEIQ1oJwz@l&zV_-_J(pVD#d1x^xeM_B?GDi96>DxJ9 zV|k{l+22f>a@vofp^moIjTnGlEP8ucu=!aTz?1W(6PX`JMf6Tl_VVKE&(Of%PuNDh zqt5>b?gUY9`F!NNW>q*Hc7JWBNm@Xs$-X?^qEMOCsg^^muhu=Y&7n=)h;MS-aUwLe zTy6mYeTQVh<;p3=RzQ?JNP;(vT{ncQGJ)r{LYRcExzv=N#Ci20`&(7+@*BRqcoIYE~24`#*N`p@oQ@ z6WPH&ewfq{5Rz(p6Cw)2<_MuaKH>_IB#5C96M|Ge3kB<-e(U*)zdoOs0mRPKvO-)BVIBsNF@xjA)7{tT%iBC-5^ETldqHTHD z%GK$xX(JGOm}t_zk&Tv2yrok;#we`T&QNn&rg0TSk0auUVyaL!;#{%NXF}>+`m~5L zPGmATxv5m9rpko0iT3Yr3EMhL`fQW7jH^|=MDbl?3&BCHDfT?z9UYe)xshQ(kHcJ> zA#R`nE@jbgx5e@UJ2V z9-igWw-MD5KYlo>&xmVlrwD1OhJ67Znt(|{A4WRh{EgXQv9*!Qsm2OHhF_= zt!l{8-X`ZfAdoTbbUF}dL$tshC6Ih8;g4iwJVrSyhx;jP3ks5NdGmZP^9vJqx1zNX zu9}PBv9XmAY7S>Blxx-vXjGo7 zyG%Ei2$;*0KJy94B&Yqt(c>oNO_r&oCtFu?yBy~OCJb8T+Cvey-HcYknL*3!++3vy zVyV3mF3&DgmT1YCQ?wn?+ZQ){zUKp2MZQj!;?tgW2QmCh&F)@F)tklT6tWp9K3kHIkUA+rCE0*Az$sCVu}8=NRJZke}osVuSHjfXYS@?E4- z$}m(aP@K$}G8k|Ra!eg(*b+NSy%qN$`63Da`0)3o+59%0zpNSB+-tozwIvF{nvF8w z;578sVGeVAzO}dgq4A-V$-ayAOJIGEhXHlq&F{1Y#I7sTwEE{EjM?;Q+e zl&9`H{-*?~8q3iNtZK;onfO`!E9naHw(Me^H=VP zXExrlQl4+BuS!LK)v ztrsqZg9)Q~rVg;oP4)2vROBj{Sqe6XWRRI`dQyF-%WdeE7tP>fEz8GdatYKKdc0^y zc7&#ey$P^r_On!IIpzbS3aue?G6MD}g`$6(`dQb9y^&sh=tDmvdh8kj{0)3KRGunG zZHX#g9)}H`SNZg`jo1eFdh@=PFQ#i0dvuDhO%|$keKe100h_cg*W!tGW7ts{5fG>o zOWWBoV||eKd3<`>@&KY}3%;#)yxJ5_CbT>QAQt+DfNPm@;`m`_(`M6Eq8a0rcz+1T4^@EGf_>WeOt(0I*feq z+e9)pNs`11cYUnXzp$9Y-Y{Q0s(*t;wxT9EQTa=WgwC@Nbm`5|C`6x4aj zlrg%$r(aFv9}bL&6IGj>WqDiNo^JWMhOuy;cXie){i7kn`>&Z0b*r*MEo0zaq$j)d zCB2(A7Y@B9ZD=>AGXxGEvkR2-IeD_cyKS2$s-jv1tVaIoORKIDUZq?P+*_K)Z%^GqCi6=x!6_@yB?bX5pN%eSrhQ=akN6}nk= zZ$B!uPEjB)8WbA=udOHV&%zo+JF^?7B|P%qL7Fe{&mMVJLM>j zh;!TJEogM{t!>6O81qRQ=j_E$-cNeOYhqhvxznK%?_t z|6HRN6>VRjhpqg`CaZz>GGr^;4}%t$hWU;Ai<}hVYS(n`ugVd4H-rCGjr9He{kui3 z+05~qZ&uu=DvZPm6|!KOAC4PynTiQMt{hlQ7xvd%=+kj2-1z$qCO zMUbNbB5-ORu$am}>1KPuUVPNGx^S)-UL~ASV@guX2ZO6FIg;+Sq&ZrRUtBcoNIdu6 zy4ky;I^y1PsCeIg#>d)T`6Mu{B0q+G_H5i&QOfUjk8hs8Y}0C?9Q=d$ zDYf(7HDqoMGZ7aGmWrc`Utb>`i-A`p9C{{y+{0Eu<7-&rpRw4}e3O=UchOos9RI;K z@$rc-dXlj?&22xJy>~`((`h8dx^`=*g#oG4%ta9W3o8|KXM%bm6Oj#b`TaYaC=N8~ ztX@yvOxVWDpD^-4)E6>F>Ni`}W%&-n3(TeX>YRlVCfJ>Gs&^tS?|NJBMG0UJ*rFI5 zQySDq>InFhRNZS{zOgO8+)f%Ud(}EB)IhUX?y5a(Rdn>ZT~(zMUhg3&>tInQ3h)Sq zFD~fHa*^w9Q0NJc;w!QWMH2w<&J z0|6VA7VyhA`uNjDY7v1bBwz76!2s%)iNg0NFKZg)3;imr1`La{0J;#K5Hz+lb(g+= z#)gz$Ex`BseNd#rK(ARX9zG2^tCIi0YR~}{^i##`-8%6Pq}>EDH_y~|Hyy3yIDP^<2vOwuQbp0O{4Q$#Qekz_)@ClRD#Y2JS= z%l4@L2Yn3L=lyYe|1}y*MhE=(rT6x%(fA6vQ0iM&skMEU`gKyVpJJU+mzh4# z#Gd>9X0&m#o}GTmqva-sKVQ~tJbk=%ew!d`9GJ+Iou%B*|MQ^r{k@loC9U4GpG$fz zS8=*paM&bGxu0K(ntpq*+hK*A*-*l_{l(ezP^@G_cgc)IpiB%}Cg74+v~zk$zI(y80feN^b!&5fUUo}!$Lmc56aG)h2UgA#gg zwQ%Q}x0Q6ZoD_VoJmw^WJi(cgQttly^WEOTv(6e#wMOU;(84&K@sq=i4|`bZj^K2x zlX<<$w^-`=JsqEDz{Kf=iH6eGYeQ`o&pkBrM8i6ck#&Y&^vP7Kl=2B73(q2jIc6ru z%8)r6$e>IPyf$Xu9Y#O;YnRF1VAYx^n1DiQt8l>w?EziSfAPvCNLh%avP2Ki1)de@ zE63)ui)kC7|DID#-S+%KjIpCI-e?+pFsj<1{ldyOXhHA5rF_%RD9TOtXYae!uFp-A zz>}}-hX!@(z^}}f*xeIOL`(b2VJsCz?ybjh!_Hyt$xwDBI^=Fr@KeSh>9&FXo+!Rg zA{$^7k0++qnVl^y1UaqFo;YQV4-d!R&&NSDi@>Cu{O)}Ti`B*VMGK7OVgz4ZYyOd zAM7#0gE2a!?SKT{Iaci?;eDs?!1J?GLWW~##R%$hHcTSq8l;=C?GZg&PBw*<DaTzA z33k?_$um%FzPCWl$<199mzs=Y0fr2xBd0V&bG4pq1U{&BKWey56YNmL{lWyWciF%v zEmd|4hYy)w(+!&N{^!uipDCr7KDeuVy+xbRFGGf+gkV(3&I2FqI@yT$YR6hV@5%&; zA=UQx+a$!NoGLE)A=WLz-8374wxuaS8L_SMWE{viJtKa;Z2Cu^?X+a{+_hB0HCW4& zTF)Yi%M}-Ys2>T!71j(MQU-qv?#V-~CBDFkEw&ROY-Q_v@Tn1dx6g(ha%W2Lm4;NJdfl38N5#{{9fWm)QM1 zA7%#N0~jkB8~^T$`f9Y?=o}xbo!`{qbJRlxzk$0@ZB8Nc30E1&hpFlAi6CJpD%Ce$ zrv{DmY})nwe6O`7!vVuzrnp5IOKl&5%`j7#IL3;gqQ+P`5Zg#vdl%MuPXkr}Rg|s3 zA=omO#}N}!8Eq9lQNy~VSAT7>QmflUQiH)BVpZ;6o1<0XWLOy_A&Tp_Y}4MKu`W0y zR?p~gux+^=>m}(0Og+-&<3CCr#)+tPxSvdqXeB6%(|q6EG)e3}$7jqPh-)St#Y@m| zDg5L`^X~q+lnEkTq2wF2#viP^ zMD(RhEXFyrJT*AMmeSkNP{W3DBMWq|k8|1egwXtFVQ=5RH&p}ge;M{`IL0=rJNaV> zc%x^qoK|<--ns-ip<^E<=>?UW7Huazok`Ajv#ScL{#2rm>Tg9Egly2~TOsAw4~ z;RQ#oc;BtD(0@u7shVSXe0Q9fn30zras$2sr%u<4WXg%?^{KdU{l^+|5s8jSnT4aM z)DCCxtNi~**;|KYwQlRfDhf)7N`pv9NJzs=BOyq4cQ;52(kUg<-QC^Y-QCjN-QR;Q z*IxVl&fe$x{?kk0o%5Yzj%SQJ#_;ekaW<&#b1v=ETvuVdM?ZJ&P`>yis@SHw62H)> zA!of2^QYeB{3u)>g40^5#_r6xB>%4~7a)^$>yK`I5O2}r{}Z6uh5iWSZH8k0q}dtM z_aL%4@xOhxA_6%8mZKkvu2^^!AEctUDgKpnQJ3g5+Crp9cBlZ7Sov=!RWmedGnJo} z7~2Z$8z;X5oxe`*)-MQI)Y>RkEZ_%(c3b{g{1mvf-v1gQfJpv&DZ$T!zpA(p6R68r z;M4l$^ZRdnu50=K|9_SaQquF&p1{?moXyV*4-TG+>d^W|s+Yyd`+=M7to_scokxvC zJYN$89}3^Bg(BJ!>t_rg@i(GJtHc@l!$`-AzA*2|je=JqFm$#eTYq8gzW?>&mInU; zyUmI9z!#tN++(gnyMI@G<0);HjH-X3`+s54z6Wxc5O#hPvN*7^@LQG?6PbL;xiKui+qTeMcNn`3eW@EdcZ9Z8l;nM!zRCs{dfz2lScL*nQ#PcZ26n$`s0yd1dAah>mg1-{c8; zK5lvt71>|>#*(3gCUXqszeo{@vn6;yzT~yv$M$_}a*7bD%M1kn<|6*>Q39eSWw#x} zZdXew;Qs9aA6erXBhZwSK>WW%b7$rZ|2-Kq(^>HbOYTMqYI`n&e~f1h)2 zGCVoiXRiBikE*Bl#~)%w&U%@rZ3loD|GZ0i&VCTfm17ORC?nt2s+TKLuhNLV_BsC{ zmLP$WmBA?B^Fbkn>idgY$`=DSXHc{T|Ly+zEder&J(bg9?z+A%9TK-Y@qBU7WT%h{ zeo6j6a8gm*zd0$^0IxSueB7RonM$^LqENWYXBh&a;hz?irlzEHgNlntd@xCJeZgyO zuzHWeMs6HKUAvRIS6)e@Eb8+a3lB<^k_+mT)t`q-T+@wWPkCQ>Me zO#u4g=)gN^s?;GLb3xANpI7^E|4f=Q*L_}}t*GQS3Gxovz*S8Ptg z+OMbr>>0RITMrO=-QQz6enCGV*4i+4LAxHO$cup4lR8zpHZf%h)!k1iE<#^uSHM6QBGho0I3eM5}FDUzaIJvDW9CMJI$)L5aqy6-5Gyah@wkAgeqRTcBq zn=7R%_O&Vd}x>!%Q-ce;q1$Rl4zkA0o^CMRwlw$25s-WKXNMpr5 zl4jTlMgG~d%zL~vEuX_cD&vc(P6sSq#MgxdvYpdzDxtZCzi^&k-(O)37kJgR`9!pY z$VVfdgXq-+hzJ=K6~A>Ycc^qS?@?>-k1=5}NWoN@@&{}diXh%TILhsn$5BRV_gBPQQx{8| z0#hrSGOV_q9QV*hZi&R8bEXOiry{b;;RWcf_UM^iN3Mc*)E4HiEPhXw`nDdz{*#sk zx!sSx2Rg4TfW)SMH%dSS#l+G(TgL|5ES9{ zI+)=M6*g5&cGi|(a0iZKhSgd3b11k3L#=WX!BlZCbkYJfIlMAG@tW5GaGF|)n1)S{ zjpq|=j{?>wb$8l_HPDGn|77alubK)T$gM?hiD~Y^A}{c^6?#UxcuZ&pIN-N?0U$y3 zJJgdS0Z>>Qj7AA3^Xjh#ka2MZt=Ppfzb|_4`xjOc3b2w1oDXMQ=%QpY6E@QpI={X; z6rr5Mm2}Cyxm&67JWAmxWD>GYF?D)vfeDQU}Z5@&(ZzI8@R^O|R!r~qc&Gx*6u29{n zh6>3q20tzO-d0_aR6l$O!WFkB782-sU4lI-XNT6s_a>6B)=uB87rYJS|JU09 z1wEFX4=rwBP=&ziEei4X>t;V_;o6*Y$ONy+Mx}-3MqYK=(Dvl2TZJ3ydz$L`M8Sj` z$*0Qg+vV!>9xrsz!kVi@IcnuZl6#ef@_#nncMGzndB$~I9vF0XB|37A1Fn^6h$HM8 zJWr)_7`daABltW}#R7r>%^vXA;q)KV2vva^FGk}RNo;2OMKN&?6N|GsmS!EbM1g0b zhun}F7bxuZ6mwoZy(0S}ysDkhcMFu`GX4L@ORYb_SFaH_H3S)4YnWKh=V*_lF z(a_cg!lV}$$5={)<7(VAG_j|9#Z0$zlT6lhz6IQkB=$yo`zaCj;k8?v^0A#2L0RGK zqO(|&6A}}%l&m3_+c`l5+P`CGDwG-=k4TC#wXnv9I+Br?D8vxZvybA%&1z{oZIBI< zyr=F>ob>~a;{i8B_D?`so$3LH#@sUdZ>M=E-^qj~-tyK}<&@Fzxo6XDaChlMVo7VcofIj~c;nl>qeV_sL$X9KsfKb#WhWN&_c4E5zp zP5@^h*L$^Q0xOYqcXX@0y?uuD&Xm+Y*w z!|KmI4XI(Ezrw=r{e&tQ5Rx?7={m7`^zPN9sSlK5CSrb3)R*3%R$TX_vg zp&y>F(yo9V4v@-_)Kq<^R0crUkLRn#148rT4FdESHzFHBZ)VtgMF}vRXXjKGwHmDT zTo%SpYxl#@EU&-ciH>DjgO?6O;zD4YS#nDYPxXmu2(#7g%jprJ>{ee6wCNiBvP@|j z?bZgLYUY^DvPnUDOdNBng0@uhlHRS1>B^T1C5(9N$!_LVz@xCpR!uNhO#i%l1?9~b zwdc}VbZ%)P{y|5^Q{+F6-txM4Tqu;T4uAr~+-`S9E-*kS?fhISaJ&^v17Nq7miNc% z4<`z%3aIfjQTw?2wQ+%UM82++_6c!WYC;8r^^SUUT+%?PXvapx(#<>E42)J)%*6sk z8?Ep}tiPz;$mpFm45WwyUGAW`<;rcZ4tLcJ9d;v0nbPQ+HM*jn zE|f0<(7KIx0yRhW*rTbEjURLwA~6H-q{%K%VmX{5cWW##jVBMY&7@(}D_x%u0~3_~ zR;ekD&`00Pj-BXM%ij3A)SSm|gDXqH@-NeD=H0~UjV4GHtL2UM=IT=p!020Prhswx z8{^r_=ol{-dXpJDpms>@Vg+i4snRVQGsz+z8Drt7lEC{52_OMo8vUdNbPKdhpEFh{ zFth)tudk0WohV4Ga70T3i_0V0j!(Q0u!u}I)hrjBmaGpr{76qc`|oSsPjh;C3Z(*l z=_x54H@(pi(Z29|IYr5Jb2(^_OekxgMj+S9G=}V{6W8!rpeT;R(d6b;lKSE#tbT}sm{6(enIW8#JwQ)e5 zL2>BQEtS}0i^*agZX_zALL4wTe@DwSUx4S>PTQgzVv}8)JA~TsTpbebD4syb+8s(~>Vt-F-{xcGMf| zf>M!0_z`D^`_@tT-EC8L*=dEKTdMw(Bhq(OC?9W=q>VGL#9S8a?{3bzv%11n9Vd0( zHC{hQ7$sfvTc1~LdEeQ$+@n5yNb9Un_C*Hi-IseD=g-~;b8+i4vm z2?l6I(-_2H2#^GlAdivxU4tWIf4K3vw5S=o8S)7Aw?_((J76~}%snbgiB`DOmI4+hdw0cbfyP-&V#+j=tJ)zfgf5?-%0 z&LSmmwT;f(*jTMqZK77Y&*gW1Ac*{I>`~x$o&`Fe!07FbQ}>A)=6luK_Xw{e-Z?%F z@7VRypEy9G0e-rJ-(gkXqOt&bj2OL#In~*@NjL*$iV~VEYTVuH!k-Bk(xh!Uj zJhm1WV4P^jQY4c_8kmkAPnyi@Kv{NCKCybH5nT_Yg*v{C4P0kU&d%?LYDg81VN+8g zWEmE8=I%Pcn6Ac`K6S^Ql2Bk#(qehpKFU2G{{Gx7h!85_Ud(aj6oUU2BG8;GmFc9N zD))I`=Kv${ORr(8?s2Z%>bGB~_x za2-EGR;$#c)>*|tI{Ng)KaoZ|jmd~3KN(A|k$>l%c z@e%UUL1WJS;B2;whXCUB2Z-87gP$=dQVc@(gY+)6SNX5F)pSD{JLBE5d$(CBUX=H(pOEyCZEf27lr6-XpWrZjpksX+<4 z-KV$k&Yp%SmNU46WOTxG-Tpa&eh!@X=~hr%&4`eJ8e3Jdv)#Z&K8hZh#1(;gdka5U zdu&LjsD!{e$f6WAjK3c7-$mL8(1$J!zphlO= zI3GIs=@#DsTkMOLFBXW;&ah;7b*gz->)hC;QF!S2k24oOgS;c2qdrHE>x?aex55Pj ztc(QGg))Otf$LR=cj?8=4Ua0+cgAv~Yg~BXQRJv{UMQC61%m!}6eFW8ib~zpxOTNS zdbqfseG430}P zU``ip6;XEkZuMSH&Ip&$`F_t*pJ78o6f0AZ-LafNs-S2vid2;g=z>$WiS(4}_=7J- zs>|bJ9Jg|*CXHVVQ`W!!Aj($Sp?+nHJ=*}Qct`Nje!LmF>JfgKhAaeD*hwjRjwDUi zP#M}v<90hAtQ-E~WTJ zRIRN-C4phkM;9JrI&0c4vIbEiYb#FEbu(2|;wO6DU zYj`T>vZ$`c6L$MasoEa3)*?f@jZZhJK2L9{oH8qPNo~AOYu#Fa^7n*&D}^f@QQ#sF z5+RboB~kr?OE`)=S-ZQAI%g*X3g$L&3*k+Sks_+JOhyHbZ?2+^^X9lJ!5Pdz?TfZx zg_Gr;l%W-J&pc=2X=wi9P0+od@T)%{B;E_8E>W+qQcLKKJYsS<8GT7AYf_erjiPR( zRw!pH2q4kS&Ny1o)R}w!`0>KlVmwolc)AxhD@V*s8%x9DWg$)M~cF^>{9{qvJtfLAaq(I3EFC~Y3d4ut311RXHsywJT4J@(k;aKBr<%a1!Dsy>b|bM@WBdpo@I(2Tbg{r}Jjzhe z(7OuBf}94h!wHqB(|{aZ3-|a!%;)t>{!HpqQARDO?oKXV09O~@Dpt}Ih{RSAR8O4j zVhcK4wFwTyzDWB{3$+z08}vHvFdcSDTs<27#hY1#LHa_eOpKzUPI6R~liD2Tp~hfD z>RaTh1B?|AfZC{TYCH=kH`hn;=5L_G;#Je|&N|)-wjwmJ%i~#J?MSX~PdaI{v~2@x zMQfMhKMff*oef+Nbac)NYIpv}1IBIRQHw!W8 zMTSQ*r?Ja4mt)^KWu5;t!}{o>n)FJ$Phz}$oEiN|MoUxhQwN&}8~P@*rj()fRhpof z8ks1QQp3Bq2$jdj^q?bIER!IIfaA6ebY`%K<82L1cXl~OlN)8kFI|D?V9*a^(EOSV zemBpABOmBjTklxhufym-qk+AG1pTlIi5JI_zirUr#a7dR!tF<0Smk7C-Hqw*-rj+s zL3Ss>Gpby!HQ(2j@2Jw^Ox`VEy6rLg;jz{{l#X4$g+Y2oL?1?L{lwEdSJvTC1aBKG ziOm%)#Tq=i9Pis_Z50Rn8TmDbK6J;UC(gy1g#t1N=x!ZvvJq~C(V zbX4Lcmn5{Ggw4Hw(Lv#6QA)i=#UL{hJ>`b(mynH1nliLVw?y-vT^Lh6OFnhCAA9}H zH_oZ#`7+4Kt5t$e#X>MT7^gT)L${dC&|$$Iet=@sxG8sap*I|pF{AD*a3Ma}NDCCd zxg!4d7&6<}!1ZxVH~xkwJBY!EQLns))@lcnGrv9#KYfsJ;Cz{*I6~Y2jSF2x?)lbp zj3NnasncPSDrsA{(ake@hbYqU^3+}I5Zf7dHX~s%2ah^c$hHNF$ z0G1V%%(N3Js9?Lp+s``{bNGM=Aje%F_8h&Ux&If<%5+1E{};42p3{6GQWOsn3?^OX|BfXS5Yj zwNpQ3Nuy3fhSqF9m$jYo`BtUZ`S3a4#__j%@yr}N@Q{yG{9;5d z3B0ePFS`#4UIfG<3pQfAkq|1;R;Yb>Xvya8?XI8W1+tWSmdi>32W^XW)0Nk-kF0Em zS?SO)-Rm&L*soPDvi`*`0=UH0w7AiEQU&`Nm6o$dsYIDQsurJ_L_ zaagX07)LXDs?=(vczbqHp-Wc{^uAcqmd;rhWV7SxWWjk3ci>#Q-)7iCxI-f|F(&Yw5`9SHS zZ46SSZBs`?t!%nNfv_5Klhc0haU1??6DJBys$x>0a|tNt@$R;s zeRG7r3s3InZPm{<3>s|x!%VlfO){#l)_y%-p`VWkQGipWK+P3Ba7j6L>yvsk%Vccx zmmxK^^JL_*Oe7B_94tZN#pyT5$g@$$4_mz8G4SjAGY05(k!GE#Zk|Z%WM=(u9=pE z^}r6|tmprfu65;P2cn)9|==02woz znZ^5HRf%Fx#U^6PkrsyY4HR)}cHZq@Av^6@DOOiu%l>3+ZiaMjQqy~O71k)(S|ul7 zsT*V+k;0ksvJn?Ufm+sA?AbGJ`B}=k;N2|zSQk=K^hwBNY+d9{V?$Fj8I?0@6@J!1 z%p=6af%Z5F&B`}sxdQ$Ip>9PiEi#76S6UEm!tZ(R=*0rcw=%R&xAyXQPq~8;$dT@E z`Y;R-xm&Ls>`Cs4JgvWOkDLgSFgKz25ttg6kvA*DvDK=nqQnj?lMyBN2`r%mjdfXZ zr1UGf^0zN30ebllk{wtlgYLyCe);!ocU;~*H~#>jjmyv@SYPjDFcC3azmfUMQ1xcd zN&mvujZLQ z+P4QcbP8i0jAOyW=9WRR;t3fKH!^>LzrNb5H@zg_PMCAA#=jm>v@ecp|K>0Ja&6EF zQgGztZRe;9v&oL`IrtBVxx3lptPp(lQg!yWfQG<&-)gzgh6`*3bO4l6oG#RC*E?Px zv>aEq83Z~#c){rQeMWg!{89h{A3no29M3Cl1p``%&a^)QQe>9uTJpm3+b&6iNgC&_ z(CGIR3R4NYg@qhG{a;~08>ig2H+NHgzP&1WvWWw>9MO`HZhoD-y}jDXFdTn6ZJiKF z%aLroOs%J*5hUuQ29^XY|UC(e|@)b8>Q88xN9I1>Y^US#Qr*N72ww z1kWe&z5A?h0efork)*R(Z!ZqH`s7g3_wB`E%B!IFZxB+QU5SpsJ_?v1>h%44!lrW-1-SzmpznGIL zldkX^=CCXVR-x3c_^@-Mm~P0{8?ICV+6=Wu{zV&on$c`XxQYY)x1nKK$VXf|PE z0<-q{rw?DJsoC4cw+#4ar$w<>gM61A^Bw!9RZ~+MA~iy{j{YR4Gq{S1>s8;8%N9ty z9H`)v|G|ibdBO3&geTNnZ1I*#ndua6%!{_06?d1D^<*AjRQ=&<>zASLI4xKG7~)Z&;JnD^P!H-DG37`i z&m1T54_*$Q1bnjRC77c%zO$7Uj=mg(LL^TpDXBHD)9u$|s{fpLs!nfGBSfI0xN;1N zPF?_&4`fP)>m9ACW`d>#umYm&7(~M0^bQ-{;Da;MRv(9xQUu#8uAGUoM@^BEK34`;L7v<@lCps^01DeXb1fIWzxK z2bt0q{12<@fnvxi12mO?S34jb#hu(-P6oIUJC(8lsW?(;p<8_u*(xT?=y?5wiA|>m zz53>VGpSCEwH|}*?i6Ix{?nEoCTjkI^6O&akD{z6g=cy51BWR^ls?)>Gk!Fa^s=Y{1zz@%qC!f@dk*xe#@PV)NBj-;yFYCJDL*j zc*);aZaS|r16F2%wKtJ%fRW%(ozuc=Y*K}?+JF#HW$kB3Co8preKcgI*Pjo$7+nhM za~0`nA3={*m@R2pq26YFL*fNv!m72Bys~ongZIOU3?V;4NnhqFrVGcj3oo7+pa{Q! zCog0v@ojs?Iq>hX z)&6hrIO1_g@Hu8nW5W$D-j=&F+6E(fBvyNGNDy+ky+k_Q;b&p25?_(wFS3|5b|(Z8 zrVpR)v}>`DRsf=n)ZuCer%bz+pM&^XXmB=}q(YjC5aCmj#rS+NEnmM_R{`?mn=`K0 z*mtWHMt4@j`6a8p^vJhbjJbDeY`G3!-?!GH^WANa`}u_=l9bS@5r1UG=6o6ZcD-Q= zs?1>rcIsnbKTnr&+u8SY&2bJW<;?ulAyW{u+0gkx-A1vA4r`RR7J=*zdy=rmXaeHO zN!w-mM6%;h5j!x5Z=}$|A#r@)Uk4UG$7UV?qe}U~Nm5Wf#u>nITXBAyCRS~|qxG?6 zf1z2sI{T&)smET4!;G!DS5HXD&xj8VGUE)0JQ*2tHO!w;Io+d$a)Z5?a$vG1nl|Cs zfhNmJP1W3GDbB!H@38UF?g&WvSTrF$eYC|9c{4_8@((qj$aFu6h@|}?iTJFT>$WUb zd#L?4w%*Qjo+&Gt?$?`DpX1nu7)Vt5ELW*tbc~5ewhjVyl$aR3GQd-)sj7cs`79qzvc_`K=4vt(2Qe5Zti#2bRukUH0 zq2OlWT6a@y39b3YZFETw%D$a>JY|oeg@T)YX-iaUB7D3Q5J^pIwf)V^@Y{Esad?QC zDj8X83m<_>b{XaG^HZ0{^5$(3XA5g-rpG_-WC>-`CFnxiKqGH;$^vmF!-6_MJ*<^7QwijplT1c%mUs zGpzON&Z82RFXeOr6!G;!UnNf^H0pVnofcW2ls*mGGEpswEpy0`t~1DZH1Xhp^8*55WiHwrqoKzB$Rk1_c{WQ&pL4>120s{lByBBR9L%xblml*NtiiauNvnzeDI`D+nxoWV4`DiaCdAOWJ;uJwd(WodeUVR z2}OLk$3$WnqGW%7_5xoPdMqmiuSEZb0Bfy3wg5veJ`dyV6j8@YnZp6~#_VH%Xc^u% z6d$Eq@|_#P(E@FY*$fwL>$u2P%R(1z>|fJ408Rz_{CU{CaRl!kFvFfJ@4o9#2edgr ztE`$^*PLHNn~gRMi3^h!qqmhc;h>)sX76(x5?%))9^I3mqbk7b8`eRu(7jhvN0F7c zf75?iBUy`SVzS;ZR+pG$y*!-#vi#7mxdIVFp$ za{`032^%jI86jb8S9m;yFe!+kVS0m}Fy-gs9-%QY^88Ps;I;r4gO3GNe7(caGM^<_ zV3qGu{42JUp)^Gikk1hRD?{X|PWQJn>~b~QbXj#(B%b|!^U|$~y8>lbS_( zEKBJ@auj4dtMJt;8cnJK_-dppw~auiJ%L5z#4S@_Hy4kCOp%y|E0K{>GH-V}+Esy! zr%1LW9-cv;97`Kl)E&L;_*p^}LQHHlr$+#@(jJTdS)2D+!^;-Ce5N zVUpU`Y!M8`QFKviFB%zLF}@q3o9uqxX*aL7*$dlJ$B0MjA|8b%!QsoH5kydhF|zXxyrp%A5FnUL}br_NfWybv0e+!*yAhb<`b zyAIgJ2i=cQ)5L4-PP9&vfswf><7U4ZamCUaUG9O?hwZUktS8XWUB5M&o<5|0?H<%c zY6H#ikUU>^EGH_dvnAM>FPci(Kai3;fo*uCWXr^0Hy2R;v0Sxw?~&@=*g=CL`O)MJ>GTOK zIl2rp3TaOV2=IwZVv@z`Hw*(PFA7nrtHkv#m!^(IsB0CrXflwFbe)XtbMoV~fhv9G zk3R!(46sSdy<=b@hA{*Y_(EGl<&A)S`Y``1*fx>;_}&?sDGk#nM5^4UvF_tW`ZPis z$9~`C_1QxwZ1(}rn@v|2&%^8dx=VrQ8d-3}^&sW5F2A^^J8-2S-uB;54Y3@(>}&p9zq+o*3pCvSMs^lmlzZ`iS3G~yP6q641%b#NXi?xrvl0Pb`mm(vyM za?g4^4&F@1SzMtTyRz>lXD%>(AIqYf%R(`C2bs5*GzU4gi!+7V-z9yvbDNdccSGa8 z!2UIqDA)z%)hqn^X|R+e2t`D%`NI8|{q+#fsj>8N!dE;$VuIRV6hV&$`ZO#N>YIXL z;Ap<^;hAalzoggcVn%~#tFf3%_4TtiZqAxlKJ3fISHIA~Co3Lf=M1br zPT4o^52|-U66in=!w}UI?B@6ybqy6QF(d!~)E(-qqy*Fz;-FZI%3=Pn=+;G4EGH!u za!;zDlb@5}WMTQ4>quWf}n7FhDq>7YA;%1jkJDpDzl|+<0V#$Q)!MIBL zx!?H4cL_ATsj!}d`iQ&dR`BGpEiH7Blo;)mz*D-UM|GiB8ypVsJUw>N_!)t^2+i98! zytrS07nen=;{}sHgAH}9E8J0rUqi1y1-e|tfoq|sfj7%;gdv&XE)QXTO6ZOe~XJk#volYKR(Zx zcs8da+wUNn7aw|d;&U1bFSa5rQ4u>&zm51X zSH756rgl+Ix{?`{%v@V-=Za8R$&?B@96F8}s8XofM)%rn1`iY3Hzve$yVso5)tFfN zXa2noe01(M2Lu>ofXfndfKAMfs6x<0dN?X{i<`y|6)>nWGA*%Dr-jHr;bsgEKyKBeH_xc zakdzCDEXA_De$g-82>O^0Q+1dDd}VSxA`Ih`OuR-mTv766b->}A|N;kCSkSpfGA6s4;lQ=U7QiWB*e4ANacqv!YgWfzW~C&-QHW^AS_}* zfKM7q6`YoS0H_QJ1o9cNMWVi^CJeK!w`cWtgKTFrZ?g%gJ{$lb$7s5R`gn6xkVxlK z&FAC{IH#MIFY;fCL;~KhY0Y#Y5d6(&d?SVdN2LexUE>R`&|r0&Fm1YmfmuWp`Sr4Q z&!uN-n$!wa=R)glWM~V;dI@)HTeo;@1m8o`3fl7=ere^?8sDL&>;k%0YEbXQk(unT zb*4YZ%+62n$3pC^XBLSL^XeX%dADTX`5KBWf&W44iBB-(wkp!fe$7nY%7Y<7mZMca zprS-WE%&PpwucRTx^vpYp^PDqgWLjPbm?ef)3EL;+Jc1o(_2Dea;eX)tZ3Y1aSEJH zJA-X&t^*$Ew+F26&})n&9;S9F@* z_)zO=H~$=uZyfiA#G}n+Mj6`|1yJm2QWzK3x*5NjjYr`O>C)bA`s0vDX-(kw-p3S) z9qMBvqhaaq(WsheDEj_;(xHx;ho@)P7Sz3;LA>cSg~NvWZI+9NVNev3`X^|c!rj^G zZUG7@6(HIkJ`Lfd0(cU*XG6F={mlqPh=E#Sv|8X|c!t&_tyINCy;R!aJe}$LyX zklWJwoPv{BrGe8>Y-;}?P+?R~wQy6>7Jy0rAxj$48C1$qvQRM4U7*q%&FT$Gldd!$ zZe`FJ`q`)uE(TDfk$GOGxzT$_ure4t9byD%PYwa~XlC(_lfptv{JI@Z&~Ud}_u@n} zP|f{u9-4c9ZeGc$f~E1*F5kC}`*6pMcDV+ImCo(|bm#c)l4HW<6ahbrZ_OuSZ6o<= zp4OGv=Da}Wp>=YTS(O6XS$FyR+b$F?n%4CRFdxsJ%Jn%sI=CD6>+ehEp6 zp2Puc8#z=x%ev2(Ym6s*k19FEP*LB;ZdNoYr2ONiWIW#7oi$SVdH%}+Llz(iK{N>kBU?~J&lfuGr(uR! zMaJst6e^MccatAeAgL9$?C(=87doswanAiWYeO`0#Pq-Xwyo;@y=6Ih@~yuHW0If! z3bk?xU^c_BNCFyoneXY)RJng{aH8Sx`AGEVU&PIfyB+&qbiyB>@;~f!Z7~31?KRJ# zcGmxMML-;21DJ{bazTHLg0!nVEH=9-I8N8%uKHH7ISs%LbML_X+u8butNDGIpmF)` zc(H=^EN#@P=+TCd zBV*fvpg0|lq+M;dpq=jNzPvfJ0Q&l+wHJcJrG^-myW4wK|4`>&xh6xN#Vyz8@D4P<{-su+5=F zv;8^!w$$&720ETe(gN_+2BR>lnuo}f6KjQ0Mz)%qGTvDS87^zpr3fmub-Gb5RVu_# zY756u&N8p8+HVBRIt|1vvjzFyDM>X?=Ecn5#GU4B>Kqa#|7pz^nyx0m{u@#-BU1|? z?O_^KpPN?ux0t;mf#S{)>4t4mZGU(hy!WWgN)hF4)fd2f$|qHI zEac;JsZs{^I52Nc(KnT0>#g?mXDC;zRWq219sBG8d2=E5)mdIxp`Ty&ajT3fEP@_! zI2?kJDDmxioJicci|dWTV5NoAL|L)=EaOy-dXe#57AzV?j&iwaV3DCyVPWASt6~y^ zu6#f15&V-UPcnPSi7!t^a7I2K;F5Brduc5tDsSRHuOc@gRX9gX*N}`4*(!O4$shfE zr>u1xXNJ>1?0Mo;{BgPh+~v?>elWbD;r)0whl`bPgJMZwQPS!0e*T&fH<%v;I*8PZ zAl+3#9cr@n4I!OMUXf+NtQPHD2ypM z0z#J6OKe1aX6+sdMu`s;@#$+fk(GSa{ifH;^!9AMEcsBKHgSi0qvecvL>TG^M^=mbc{fFdnJ^q6h|3)eGJ`2l}C~J#V0jdK0x+^IP8{zBdgGpF}8uB9x zdpb47_m|A#B72t(IH3`@B{^d>o4l`Reka*FO6-3;Yn%2Bfxn*hL_?LCSzU;gS)HXt zRp>=fcK>hk$T=s7#nA}n$wg_3FWOz;LC;CvAWr>W+7IV4%k7TwQ!MI1|5eH={CNKw zJZ_Tgs==F7c;Udd(J7m;p|DKFZH-Q-VB@q=)vvcnKgSv^FRrz#wM__AX}bQ*g3Gzz z%-QJrWFtM^G3aP#x*Ux|?IYM1E1X8H4E`w`jf8-Qot?eR`UcLw#Sr9&GV9`rx!SKQ z9!SKL?nvlY(^d4tHz!BU0S8c0HDAmO29us(v)fM$nN>-o3h-===?|y3?)gmkG3J-p z5`et&QaV>aDit=Uq32`dcyj7U?bj5c4!}_m&P96{y;ecHEkb{J5L@w6TdXr^SpUlUePx3x!QL;n#duY< zqvm6t<2D|FJPcxvY;@`md%W*j0?W<$Qh=m@0Bot@xQGf87?npctqdTv&&A)i#?l+toO%@fsdNllC{Eag3Tljz|8t8w<(Ov)fNjYp<%0qt3-Ah}C)pZO3 zj4=G^C6#ZJ62Q~Bo5Gx6DS0WyS+{ z>)CIo_FToU?$~+Dg^hH#MC9~ML=|j)Vlg>f4%t>BO4(spy?qu5-0y8M>ww@W;8rFk z`&_qddl4kx(`8l_B%c?Ebn!}v#tZ{(-0ao3*%tIzXE0B#-OE`A2;kQYl@a}4>8EtO z|=% zDas{#i}xRgU8Cz)`)T9yP9z<$7if{s1efi4zZn@7RR<{Z8(TiUGNZ>EP9;+2&7`h5 zyLb9&GJH7a0K=xsd^JD^UxUlb%iHDPc)5{YVmi-W-mXtXKy%)$`r*OgQ9n}cse%st zGmNg4v+cnk>Vs(9x_6Ogvad{izo_mntwj6)_-mF}pklq_s=c)lE#%oVdGo_J2W}wA zwEfM#2zBhEwQzZC3zpooFE<_s7i-D-W8}tm-C?Sk8Xr}cl2qPa>*Whzaz&4c7Mb17 zJd6Ci(0<#q@phAxVdsKG{x+19e)7`WGi4J{zG35=><*V5dt_;W-+o>hA?IydfI!&4B^CLtZH8pjb3Y#sMYh;4fF+$j7#bdy9-+`^uy6)Hd1Fh`O^;j6F64^+uEkK!tIU% zW&~sK8x@*}CTqiIdL4PABO|8hH}SPc&cR6&RTWRaJw|!L`Q+snSB-`T_@5ZqKNcNi z#z(wx@xsOb)Q7R){a8MXkW!z&`_Zc~c%S8W7z8k#{A!h|u?BW!>1owzHa5_@y1S}- z0dZL()5F6Kmm|5G+5F=fbACAM-CwTe&wfe|Z*;oE+p+WI#^F_?@a9MIn+ogksrIfL zMo!}rx{@RF$l=llQpizy#HkF8UQCN(Gv~g9uO*;ZT4XkfkXfXmz}i((YTFonr1+eotGQ{vm&a71D`%2ezz!Bh7j74ysQLQsacF{nv7u`#AA zJ~*AgNI_pfJyu|oU-LCf_OP>(qQt{q=2?SEv41zjp%fmNwV#H z?@=ZU$)KPN7blB+_e2A%x=g-`Fb1to3axHmM~KDK*h3)Nzq`FDwMqcCUtp;wPKUzl zcrbGgc+5^XdW%w&gAic$z}F%M(C&^kDn&fpK8JZ^w;SVn)I zhAa4Kf?{|sx90Xz&tTM`F6R4p74Ey+Gd-qRF!$hcn|8j?1pnbo2^wKpdM{SH?IVrt z?HXl6PX+?5ieYJ?n z!g(sVq^c(mSz2g>h3@mSWAM41GD%Tua&}eYv_=*pd5Y_dS~>hxbhuBRMPK}DG9fDT z*s57{uk_&g=F-Nw&Jv?b3K@UVsOg?-EwUtor^pTiP5A?O@(6Vq+^8f0p26=JEbIM* zPD6FASMP3%zf8TUpEYDhsdo*^^Xfqw&{HP$ZMJT5Pm^eRp=pPWEJ1A!9KfY# zUKj9=+?996+0I3e1ghZ7Dt*`WASXxBl3Cf6j}6;{wAnp0MBMwaWDxpBK~+ancyjY= zqNq16+>@86pM+E3#sm{H!(%qDchaB7jy)|=c@=JEh7Bpobgu9dgzBExevL`O4Gmo0 ztxyZ_nxAbC)&}>nxFOFl_!($ujMQsGtmP;tkp%2IH&|#8a-_*%2HQ?F^>%a8=*a3G zDp%>vkQL69R0L>WWT^jtoV^85mEGGttRkSaAl)D!(j^Vj4GKtiBaIw7q(MTEZfTM3 zZt3m@0qO3B|33Qo{Cwk^dFMMbI-{rdx$kT5E7w{K-Me2phlj(}ZuCUhBJFBWI5q?@ zlI&qNPiOUvb&}4PeR{W3P7hdfsEcKyBG)NsQokhS8Jg?(GU5zkV&^h6oVOe;3z-q9 zt=a+6tG);;PsRmvA6B~nim~!a3{6UVh`6B$jUZ{NpIU{v1QeOSF_PWGRi?G{?9CAP zdBIe^(HsE;AI58XVH`Mpo|oDDBO6rR+3acZQ<-3;S`+>=``uQoq1G{q zugeVJJSEBBlKvFyg_N_y_AWTWFpEUJ!L?B?NY596X*B!O;yEFYqtfhXE&A}{&~U(g z8S*D=7SliXdW={4ZhK9avo?AloP#SLGwWA$G99-<`JrO%-3jc4Yb=(sS+CTJbUdYl z{MV_~%Dz4oftku{U{zZeoGz~&sTi<&<;2nz`JrCSFJdc?dPAptN9^$+9u1+sR7 z)VCkv%6IDW<$U4(G5A@CH%{T+#!nMWZ_o0t-nvRMUzt4ni8yF#k5joxKTH;7#SB~+ zg?0?8VU&wzW$SANUOkAs)HJ5-72F9K)lsrsKL;AaqYW7<#OA>MEEOc3p;Ul zsx+G`7rRyt7gt4FdQV13siTM4K!+5+2eiPLP)l+Y_g4jRv{xOK@9FEUY`pb0GS?>M zC+0iDHrzdD-Z}5Na+UZW7H4;r)0*;+#g%BZ8&*W(!rvpVZmuShu$?YyauLotvnO_zO;sh6dP<*ekzW>dz>Qo#?v&0~< z!V-vT9_z8{YnL{639+T92?Yyfu3I-7&ET4*w5jEhk@4BPr<3yu-I686%{F ztVPt`^^A*@W}Mv7ZmdP)dvUb5U!#hN1P>t~jFL59Q_>|=C{f6f^fZxvOmh{B+f9h@#VhNQJ->MW|mG`{1$Z%6UE+{(Tq} za;f_p+h`(H;ktZw7%v5|m%1V%N-apd^61=ORq}eg3PZ+!*?WYW#Yw~`Qv=zeB);A* zn!&D|e9RooX=r~_YI{%^RFc^*O$?9r)CK(v@LpHQNZszddYU>zJUl8^&msBoiS@=$ z3hFFH7%5DV&i$5#ugk<4K9LzewCkKzkwSb67(P4$o`4Esma zzV>X`>$EAB9^5G!-fthZZCFX8pS0KGH#A`&qAz$>Swn_zcFwgIU%tR#ZK{(@Niwkt<17k=dR?} z!&0&f;h_Au>)V5M7B(W=Q%wO}W%u_=%HhTGE`5y+J3{MWZ_6_L*OhBnV@;pq3xy+< zu|*8@b5zg%_Leq3;uAE1fciT(@{of5kMZcJx;__0(0=d=Yxnh79Np*h;AmJ40%@1{(-65VbG>kft1lU_kdG8L4aqXL^Xkpp1B_OQveL6cQzeim>}!p@M=!1Tlq)FH zGMVI?Wn9)yReh9SC3v!zeSnzhJ-58BO)hh+d$ZOXcXN33H8y6w$$dG?r*fBM<#NAy zEcpZlOcj45zE;YIMt%r{*99}>W@{8-$(V9nfSDnJJs-%Ybi&sjE&2O79TfQsh%kjP zp)*!JZH$&E$T9iWylxZi%R@KJ7Q%O#dz$j9DBNoS%d+cFD6&@>%ivcpF-d6%)CwF&K7Suao$qv-#aS2NoYucE5w!3d>NG#SyrJq@C>(+`NubsYSvDEF+*Vnx zbK9wF3_AseUMegfjWyV^*%}+1)3*x{fkJJ(^Wa@`mNL9B`ECf8H%XHYRT=N z3Mk=8cXTq%xcrxu*^jYs@cF6;>vdQW!e)Nhlio)@|aPGAN%e+E;~7hH}8_rQr1 z2aBe6ZvyQRD~16qNj+gBB!KB;di$A|-OR{+fm0B#^}ut{jufXmoFUArEO^|x4X8Wh z!9;NF1mSN#R2?pVMoqNu7gOc1lyPD35xXoS6@p@j#;Lv>%>J>OsfclXHCDN!v&<@h z5F^;i7t-=oZPunZaU#VN_ICOphP8MQfus0T81HJY_;9S-vYClHis4o=FS}{ z=H?mS%I2HCqtjP)7HFFH&r+C%f9fhmombN%t8l)Jf3vRiwS5mh_&_rsx=%VYSI7XX>3ItrDupJLAr4~UWBbz#He`{_a7q-FCV-xp@jTs|5Ul9QM-D@{XWg8 zFP>QnogEwVS_@VGN90p4dV0t6lB@bH@xD`%PR35AT^RBFM%5p%n5!xXFC3SVf}gXr zc0HuiTSG}YqMji2?HgY%8AB=LR}xS=Ux*$*i~NA~>O#%9YbhW;CimS1D+B8s zoiImIa$Z%na1`JQnw_axkL8^^QVQM=dz%hf=7^j=dMzHJRf3K%-K7R77y(CUtoQNz zvL93RCZzQ`2qo5StVOXu(Equ&c~A;VdAKc#)s%;7gVepf1s-1igc0D0*syFcg{Y-3 z>VuEpVSXH()AaDuYcyTYQQ;b$dcdEG;p9`Rg<#BR6s7Q`c8Oo)pS$pjc2fA^zdz|W zt7zGOmIPk#L;&jhOvviGx{=EJkrWtI*H(U5V<@cWD*DzCp@2u|G4Vg4@-N;@j3+)` zj}Uz*OAw^M>t7ae$m4%wT6sBoVhw#{+&zNbF`(0jp-KAUg88+89*2P6T?5577JYv% ziNi*a&c01zdA3|+`{Rzf`GfoO$u2`@A}BdalpZ3a1L5Rr)YR1C+8VK*6CBz|qk-#Q z3H9}n^4YS4&WBx88=%>(klPX+MWZ}?G{DcGZB=g+ayLPn{jl!k^>sD*(%Je|MW*#L zXmZRN{#NB1#7Ov?C!5ouK?*ZFr1_DCobf>NjzU3hP*6~MAO_WCWH#H<61rP=V#_

q3C_x=KbI`^9=FBHELW71#q+sGb&Iap@zIt0KXv9a6^=W!S)0YlUay%v7^HK2T?)cD^YdvAHiHF=5hH z!pF*7^EuJ#XHmD^_Bth(yLl;((%Jy=NuF_Y-FTU!fY`v=vGi}V&Mi{h>J4CpF&q8* zW@%ntWfNN%P}XRS#SH-oxb84g7`N+#j^vWbNfqa-ol0sj)UKGX##VR%fh2J|L>JNy zB(O8gK_)aoDcM08GN=qnyxF7Du^nnL)9X`54lm??B4IwaPCi^~d=-LcaS$hl9Q($? z=vG)nYA=fiR&FqKh1ASNk}-UunUaL*XY(_3 z4GALcFSC2Iv9&lPr%E(4d7S8nM&#~RN;Rt5dgDrSl=4$HLbN9MHdb`&e>ie@ca~Q;~ML~Y3+GGvdX*mjOPOEe(`vh!O4W7G=cV3PS)zSwC z@9Q`5*iUcN$C|9ZSud6@sX3^M7HZLGj>X-7&ne`$wWLsA zd5U_ZJSZ+HUI8G#(ij@AhZS}UcMm#;~5i?C^) zFd-X|E%DjO!2V=5z97w1{8~ZBk^D(a+hQ8@UMi^NH?tRgR~BY(AUhz;H0K{7&y*Ai zOnB=N&(?F^514+uu6cBtBrmEBx=5EO%cNx)G%u+P`=;Y$QyQd$u=mmvIcr@E4eL** zpPTB5aCtIE&Z2+afou%7t>K|RN<_-jOp9_!hs05APn5QSp7#d;NI*-H#Sb*QYu1uozSo;X zFK|7~3ZGIfGkJ44siz}Q_{uDbPCXj%=`EG>Y7&-E%mDOjHXw6Zg#NR@VwJ1m_R{Qf zqVaa6e17!1{->n0yW0uId^lt^dSa|>*)e0brr;O*=cJdG!)Tm0eUm0NSGOZi9r8Up zn%*TblDx%Laa3vCsd!ch|6Vxig@=6GM}KNk-Oy_u_v;5KhOKlu(wKD9L6maI)coX# zU>iEhx$R5~eRVt_23m*nT0I+JycHO164y*yb?HJj&nQyfbotq3Dmfz37!gLjE-y+oy+$%$E{5r zK+RD$o!g5vXEndzZaX{s$1V9(TQ5EUmkng%b1(P-y@RSoZ^>|)NKhGs^5*g+0yP{> z#pHm6iMUEf5y%`30C(a2*cV|FXMKJ;?KhM6VR9i`!k0fIa+W;L z5u%pR@28d@mey!2%|@4*+prH9dqjfHjEJ9nOU&c4m(Fx;4Ns(s-|Cs3Y3MhC z)b`;pP%9}nc1TmB^;GqBj#ZAJs*~fc7*I5psQfF~2o`590u%tU(2_D3|LPv-l;uU8>cFr&amG$ zic;gYkTr|)_C~vgZVPj>B2PIUDep7ICxr%AY=f7Qo)6XxkO-PPR>w$jn8|#$nW{=JBvyy;e86ja|d2Nlzt$ z8&}r0i1I@#L7l14+)~Aw^6-XdICFJh3XEXY^Rd}oBoK*?n6p)aG5gusrdO>n+IWYM z;-)}e4#nAB{oI>kwFDXP`PBhFF_&G=_4TNNN8p0eXO2%w@3wkZw#H^&$Ozzw16d|~ zm62?GKbGJZICAqdb_MU5i|iVm*sr>5-jn~y7vb`+Dg$WnWn%$F#Ryf3t00(c2$uGp z7`x9ixI~I|^3BsQEnZP+l#9S6fF2-|_8H`N`usryH>(SN+-AAG%L3r_Qow-ihAJNu zQ@Hc-Iw^5v{*(?^T3WDl&bmojk61P;-))$B8OEB> z2X05pa&*<)_V0Hkd<4jClZK zc6n^EOFtzYhI;*-9r)u%R@x+uIoL4y?|BBZIZ$v738-uL!6J$K# zKBc094cXH4g(t_81MUviVEpTbydPYM%a0ZFRKh_BA~c1J)5un~nZj*sFmW$dnnE%% z4E1#^Jo~dBV0-YK|L{Hrq+~;A*rAmx8(g>4ivyV2nJ%qh0_@hf1c$(#cDYWXn`OQgKT zR_q=~e=L)7!P=NIDu`}WL3HyazqJI%|J&=-(qGrDh$Y7k!dO&dRR7}G<1;HldRp*u z-c{e#3v!pmnS^^s7pZb1p zKueAJJc(&)cOgW8Rb!)4heZ3#XU^TFgu3EglrCTM{;+2-W2`Z`*pi@>U}E?=k+_EI z!AwWlrly^2`?L(kn{L_lS52YX^ z)(SXRTh3l@Y1*>ggeKM&q0g9er`Pvkdg|uy@t^^O{a*4w4ZRPv9YzIX_D0WTy3Qt% zN@qA0K<1*nO_nd2SRIrU;xQLK&}*_<2B=u7&32Tyjuv4X#&8*!ZcJ9$q+idVopW0! zeLn`OaigkkA0*11W{n2c4#ql04*J@QC&uD3+M7za>9gf_qB=w-==KXSWpxnyBCAzU_BYfOuC$m@l7(++68v(%7_==}%_kXE z{b_SSj?->M_+UcDAP~s%Az*t57a zd;Lb%<3J>s;5>+(Q8~4*&cuti~G#JCt{lSb;b?> zM+KbnG2196aBx%(`Z|!E8p%*ye=c@7EO#j!GlZ#VR9-t-BjY9K!7t597KG7s%DVv_ zk%*C=zQq9Xs6tYynbrFy?#5_0d*pZQb^UMbHC69Y=^A^z6vV)TD%M#d*dA4Y;+>&S z>kr6VIzQMvT$bqj1~!xQ_U7Q&BUK`k9Iy{E0o;lD_?{OMRUrT13YuPDJgy%6JZ*q9 zWvt-6DVfe1X8#dE2mWH3UwJMZDpgG}O(+SsBmtI3PGcM(r)VM1ZuPnep4^5w8s7>zyGCLOm&-nV`{@h(udkU<-(-?nS)YTJ5sM z>!H&gjrkQP0$hvx<9O9*6$F1yNueleAD1fH}ATEw4)S;+a=4jV2+S zgnw3ZIr{zON2}<^!H^q!aH3icei4wzUWDb|YrJbPHxb6R@PLfJsuVP6kW1nk%kIZ> zyUdHkl^OeXvw?{-i#NsH+3r>B>lB_EMLhzE(@ZPqz++6mEZ7qKq&zF@gqZZDQmsEt ztY_tu|@)-(1qHE7a8%2TrfSRoFM+mZ+`tv zZEXH~F=95o&$^$9u}-;3xVcU$5*#Zt%^Z%(Izk`>WIpZ#AND%6yr{EsTQ3nYxu{q& z(11u*dOP2ROL@UUe}=7s{|8^#@);WB|S)FeIVb9Xvqi|t+)d2{wdnb?KF?4G6uePIuv z!wYa&%#Wn^Um4wKI&6*0;BDJa?gXh1gDHRfxso>CpJgMqmJ%}9Efyo6J&0}g+J~AK zg8g7MlL8|=AxqyMtJT`)Q$_R(pPhx^9>lMO_`F!>ZBKt}se%?Pgj^n1d68@E6A=+%UQC%b76FqN4{lL4%xO>#|h4L-1Klsj94lTQLAqCmLPt=2r z?3v7=)aWhg7j_X_L#;U2D4G^FyVXpUfc^QX-!d*7R=?`K@j`LZc1u@NOU1!VNRZTV zB=7kbrrl_u4PkS%T}lNaA)HSLly87*$nXS2`2;q*8d0L*1t_?$Co6DqUJ+?ty_=X{ zE_a6}4niath#h##F&pkjH8+YAAY>=|6SBjrA{$Nc*kPA6T*xUKi-3N1E8>yv6zIKn zn?_#bvanR`R^a7sDy!j428Tr=hGjdhwMK|c$f9bcc(smNIF@HVh8 zpf@>cy$0v86{#I_QSW7c5_ZqV_wT%di4Ljn*DMJ*1ddkU8=hh^my$)vFA4iF`7Ee% z+mNXtXBwxtDe?I%QhLEt;*6{fq{JMxt+&q0V?WGREx7UD565`tz$!t>s! z@tWtwDg{^3!X(PPe86s$KPukD!LjUr4H*zU;dqgZKbYD*R$LHVSIjPGo%TheL|<(! zv8p@pOB9W35!$p z<8mP@)Rya1L;u^sjz)Lf8InxZXy?3aj+D0sc=<*1F8)dgBdyvvUzU*2gWP zdi$FlCB|)&p1`(l@!w5RWQQ-p)bEWqF~i$fEx6Ooxt$rBc7MIf@Qd*DZQ|4o_xRWyG4*}Y8CP8`+LD^rMbS3C)ppFt z^O3RU*j6jtBa)u&{L0gS#xtt{d~rylQzbM{{nzM0U7xq47EONk{Yv*r)Z&-i(lk63 zG;zntVmP7?*JF!O{m`~%?}O%hy)I|FCwUlDw9*V^wrN*-x7Qb$alSUrVUHga2L#gv z&#|Dxh*sa5+Yf`llIQ9OEcba*>Ve`74z7%za%Txm!5dJk5%{j6c5&Ej$5fK7!P7C~ zhwK;VYhIw&7Q8al_N1v7#1z|`&l|`F)G=Ez!B1@e?!4bjyq@ZR*GS8k~I0; zsa;c3i`j;5qJCT`VSTOtlBoYK)2cc`K1?+?=wpH~0_W}cWRZPFN7F|<6)(RVjd&yJ z8?W(v*?v5!Tul}f=y}g<~kYViFd&}`fVFk z_hMDo`8ZuoobdQ-;t7gwCWmCo?A|EHb6=?!o|sRg(JY9X#A|QFL7Py6!b-Y1<8e<+ z;jqeJ3q022{iUtURzDZ?<3Vv^1>XX7XfJYeRZFvae+t@T;+^GDMk)}N6n!aVbSK^X zve)amdeio!xhEsgH~4WKC!ND?k6%dWOe*^_!}` zXH9l(a&IYE_a>RkkHfI*vW_S3E}u#q?HVr<%2Nm~|4BT8fLNadQ>we z{WTl=TON|^?|8>t$0p;YqJFo<*R91L;30nZ1ll^rl81{vd6fe(Dk51HxA-d9^XvIX zN?+HA#HIy#8wdzMdv_MhE~(s6f`06{6v6L@X~;h-=OCjT^;fSEFI-9Vfh1_nV%ZHgI}8wz?Q1Jl{+q(Q=H`S&gLB6e55Sbz}|!@#DnH6 zS~Rs+k-1xo`^2EK6l7UnUe9qhJ%VqA^x5ze|GZdYw&u^Rw1aAy1eNZ@Y8bY6tn%*t zDUIBQAYd#p*btZFIe>AybqZcKgS4_)H}GEQNnK{hDWn_?28RY}kI+??Wuw+SOUTE# zD`4nhHZ~QjTr&}C(|#urBF1*0I!^DmZ6*$z@=kF66LkDYdf(P8DV4AA#d`Vg(g; z127gVJcO&bkpFon;P$h}qs`*q$K!e?a+N=Aj^>bu&|jNrGTi#U$&6Q& zg)lKOMRLA3if7XIyGCL`nAw`CEHq9AM4Ad9jG;Cg&tq0VoP+JV04dB|Xu>NlXI}(= z&@?U>ygAbb)DPoMpe=T@#$Wr~YCb5EYJlS1kY4@3;k#JWs*`k4g$A+E!5vQe5pqNO zp~0Jl9$Oq0tIA{vCxqlLE{N9@M4!_yNI60PmF(Tcz-#A|vHPq*3+aV?26hzPRr)7S zLwfb%NBTz9wWrdI18Efo1y9>^z6E%mOLA3tB7W z-WVnYmrK*!;@OBO^2NxX`8<~=WD$$wBQ|3m#4vWo`QJ{&VDVw8a3TsRBA_=)|Jn~p z7#1Nhg30=aZR6ioUHHeUee}=w7nf3xu}txxrE;G0MoCouYeqM4C`@zF0(y(TYvr06 z8U&z~C=u%6J8+JziJ@L;NyiUJ36pJCdnG_gD{^LgDBXWn3`ZGcu-Sk=MZ=!EH;J1a zkd^luyV$=;`~LXAfiS~n8B6|bw=wACPDUZSKL%8iKR)FY8XA(yaEfBvdMha&yIhK5 z_G9t%%G7(kfX6szqbhYt_q=h`#+@GzR8aB&wIehhYO1gwuIqO0poq82OdW9MnDu|B z16gMby*(+^TH+y;A$DaP9Re)(tyjBSl>IwQd|OCsO-^4~ir_5o3b&m>88chkLL$V^ zW0}cLM<>dq61H9?8+OGdgKg~HP_KR7b2Fs*vDftQ`d_=&t=2Q;;uRK+)w*{5Yq)5+ zk6#JBP^l}=Q%0s`Q3Q2Hf2KTUBM^4BHsUgur5j=3n}O!jQ2F7VGi%h8veC;}XIDZL#OA3HEm16n3w<#w&{NdC}fQ%N8-I4&z8c2rv8roCYAp~cBh_2sjm=RCn$`ALh z;6L~-VS{cNdBRw|Fuc9zucZj!B~@XdK(k69xmI&i^k zMG(i!$4%4yJnpEEA4asoo$}7|JdvF-IyPO%QmipTbY3mr(pRv~D7l-dx+Y%F_7eWsj^IA>eK^=k%f|KFk)2ef)vny2M) z1DWDQHT+Y>%_YBj@#2LfTn%=MV@r&QW+Svtfe$2nSc`#b3!gI(r`iDSIUArcJGsnJ zm-TYCm{{5_k2jX7TMuvHKJ1McXPQstD(%P>nvArsivs>QRn^t&m2Se+8c5~uOHHS8 z{UO`z3m#(;SMS!^g+|sX-nS9%eq&yO-PZmlfUN5}Zb9VlW*%$$_GZ~Q&X>(dx^AF` zjMRF51BrI@?fI8#0+`J?`vv(5?wO$sniIDgG%_Oh0?aYu&*uA+Z9nEjqsM5pSIHZ~c>FNNePqGyDfIJ~N#x(&`SmO?u{ZWC;29W)hB8{1wUEA<#1n@mNI+ScfP z-q_6ZwDeuK#Bm9&MYBB@eICGA3C1?Z8zu5t$Uh&W`_I1~9@`MCOe_fX1`l%AfAd$A zU@>1y7aX~FprXONZ~rP*poYtUlNa<9lbd1{*Bg{;?JWvVX5thrH__H919n8j#Ciq* zo|zVum$>+v89@QOhG?{-$Odj zm|jGLE`7$TQFJ7$?@09%+rG8B!zBpa#)c&gabh}j)7 zW=ij5ir0YKq!_9_6W*M0_?#FTB=v>IsRpnekgpiOEV_p82mENnXV=k`nm64Tjx))o zc&dHz-Nb+;A*At$xhO1r%wl6AlEn&tv#e=dGCN3qtTB6H3%~I`sF^q;re^h<5lTWO zEMif68q_MQ6JK^L50Axx-yY0V_c6+BMKxPtZa*E_z(GuUS!E1=R?*&#pyX3W;qrb6 z$lfghiZIV30y<5!ktvRvl=@%J+jHT6CP_(&$i+e)YrH|(> z^M(KicOwKiwa7S)&N>hg1WY8rf<7m;3`4L3AbZ2fn`15AXXvGi)b;2)xd>|Zj*-CF zDyS`Z4Mrx~4r5FQNtWr@8H@J)T3mLkK~$-k4Fc_r(mK3$7)uyVIHh)JiQ~ZtcxvTa z1tJziIHf5Er9l7Vkw~T^Hs{JYBvp`hv~oJ1CO+>d>`tns0b!Te3#B}EnItZS@nX*v ze(MG(7os6@F#FpL@`d1Ul=|_5H>i~d04Oj?Ft;{1w%c0Jpf5kwHsFHS&5oaDHcwa` z98Av+8GntOR|C^U(>|)P<`ul`!y_3V`zOT23>|AF z_!+z}UyeugMSaHMclSKU#||`f+ge&$9r@XHi9bE+GNm;F84G)p?$JSDg9TD`M<9Oks78)24vR% zEK=s5uldcq4BglO zl7I2Pzechn{y=X3log^MDJ;b6V}Ah3wheW1bs zT@5Ag6WX1hl=pzrJ9I6=&zjMJ_>STK@3;RS=FNX$t$u%bzK@rENi@)D_`No?zX#$$ z+2#*S^RL<{4J_sah-qwaryHPj}p%qkH7kp4W8_;pW#jy&}9B9b3P z+spU8sJ-{ke_(|FvX4Kn-U!q&r5ejC`Tcf*mE(<`cj54wnv8%{{efmJ`e`G=H8nb4Mx<)E#-)%jZlP;1iqgFIvIvJ#(D9({$!PX~ZTX~--V#yG zO7!VyW{;lS)Pje0grRDQT^a#r&9D?h2oQA~r((#^U1%5q*l6$dhAZbDl`8APT&{>H z8r0$ZO8YOeNmx$ZAlee^i|ThL*CDx^R8Mm;V_H}+Wa&$X3a2dE7#hK)@X1}5pBXtX;S{}_4fqvjM6 zp`fWW5tqy#T=u;>nxpW&Kr&Wrx@JtJz;`#hN=U~vPdvWIr)sy$j)=;k0@6aw0IX+x5J7fX5sMS;nKkt#SNPdfUzdCI+c)_WLtnCJQicH)f8=c^_J?B6r4}4o@*GO6$Pe(DTwEYej zb&U5QF0V6;+hLcY^J-i(bCQ-aJYWlwY70XBeDaNi<&8)TW;x0-lR^LN&(C31MsXBq^EbLeTua@lDX1Vl7RDgJ?u)u!3&_Dy) zN;b#SX6Zb1p|W{eZUM|j+a&6XVYVo#3Juqc0Cd4_LD=N}y$zRo(-Y0$Ue0X@Uf<^y zjEzG8PYfqRRLtj!-kgxzk6$p@nQpb+ejI%9VZ4o6q2gO9*s`JjeTo0ES+z0@d=)Cy zFs5L2J1-;%u`3!?2dl}1r6)n{kM5{vDUpu*v+spWI>W}IGu@`@uOc;egBY@0Rz-42 z)zBaiaej}(E>ByRM;Sdrv;dSnUgNAjWT>a?e}MBsDL7To_wr3o^bwd?F#)-2LY~kT z#jPb~Sv(nwS$pu#;n8C2n!y(N@H(#FW7J`?7De9A!_Z^pADiVIE7sDB?Ifd{4n)q2 zpzXwIr<3(UDm3yNbiBKfI9~LRFeKq6TI!SybGzJNrFOQ{afQvpSe6i&BpKr~?%%bg zeWW>eevq&}B<6AWusx$C!7~0O8{7^{F+i?^*#kwFYd4Q;8$oyXm8kI1x(f|M`ZS9f z+0NthqqRcE?!g)pdu$yX%9{wRCs~_M%)WYuPXF56f~ezCaNo3Szx~?wB5Gw3T;ov+ zU-8A0j|D$Bn|s^J6<3hl8S`L z$LsYcCSsw9F`iC{*tf(0CNXbH= z+o=Q`-nJOO_Yp}aNkN|H0S4ctNr@cdVk{H>`l7$b@gwH4yu}?Bal*Z6NZ>@B?Do`q z$%Y(wC_M6Hzb>g=I|=h`ShKnOou7br@Wjil&4ycNtT`7exhY#!a9Wl*SRE$VkVum* z?Y9$TEbDK>jr6u`r44<2q^ne=;&Yn@#nzB!APPU1}{wl%pdpvxDdB; z=ahwCek$TcSkBL<_RAE&pZeha<;ov>D29A@4g@Er^k2t!tfJ@mtL#^mJRq)kO{GF6 z&6d0UD^ghe^er45q`(V4{$IS{AD0F7!V+?S`7r2z1d0O-A64e#_Wh>b`^R}kB!lx! zzC7^XyY)NPFnyxSm_2SWnTHG%6fN|Se_u5vF5p*xDo=b{-}GueSnk-k zJc(??g3jsI&;owRnJlXan~hP5a;_hs{{>cHu9Jys7cZ`ijb!HxiotsfY?E}+^T)$7 zw;u(G0EIjdDQ-V2?~Da$YA@D>VvH+leVN zp?aI3BJ}$*M)fz@xNhcT0|&huQEa^^s0^Xx&;=CyN}#$Mk-ZISzoU7oEHziVwKA02pwokEh|7X^FeKIm3FhvO4#}!>atREIW)k1XkWhav z6)nr^xc}n9>E@Drsdh`u7xC_s?aHRWa);IDl?4y}Pk?UsBoF?0y%?al@pj*~`w)Za zCZ=5s_P|#bR>K(*OLatfSlMxaG&@31Pp__J+;lP_Q|Z(SaK-p&VhNmSp<$NDwK+-H;VZ zVB@b$tYjRqg|BA&JU{S|?Hg1w{qkf&drYq}LxekL`a9n5fH==i6qF4zUOfP`XW2jj zD+LgYuU4Yy)Cl?_D3%yVFbJH2;YP_~Fr`FydDz3iyK=ip1P)%OAs7zQ(O*#v!(fvq z%*rts$XV~_-pc@rPA}eHkYT0c2L-N((~23;M`H>2X<|W9BH9AcVXiv{@&Y;XZ;m_3 zZ%oJY2oH~DtmMK{;E@;`0QSKDb)i(mN_2qb;p&~D$^{R6^49H z3w~_%fTHt1SS8#?i4T7iB)zZe{y=?CE@@U~qL6B6QON8$_vlWQs%dE2aPeC0$5cN@vc+o)-Enc6da>70x_$XzbwYaW>ep&R85WexYQC2 zn861##o`3m`d5nx02sS$XT^E}nL=gN-^6C6D+pD-7#7hqZJt%Kz3g0suyU;$+Guku zvwgz<_deNBQaF>LWf*|Qw0-})#Kgf2_FZ9+s+a;_;u0I`rjB`PA~NmEqT44Rf_nvM zB-8TME4v2(C=`pmD6h<9h|B^Sx83{{L1@W;zt*2@djyZX__k1sSFXCivLlr6uq7}8 z`^Urf(`Y&t%L0-Y2<*#Z&!ND-ENNVzs?NXgP5gx!3H8c{qRUytDktOC}3GHfTBlNqMU@=J#jtBAz-)Ke_i`d@MEDv zJ(Te!jD!AJDB@5^D|+;+uyV^J@f26)D9|Dlq>#LpW-|+o@G{?n<20gQLDUlv#Vdd8!V*h8nLyp5?- zwY+FQxv=+-9F|xO@x_lApoBPCAW>9EXH?6q$BYWN{f2`8 z_SiiE?ZdM|0dR7Kt;o(C!4t{a+|ZKHGm2LybJ(QOifx|gf8rH&I{X$s5)cQ;p=Z~5nl`^)1zE+W87*y-Tca^SDZJUG6W$)FyG0SGdwRV$~0ywE; z|H;sltOrfWG?TjehC!Z%mgD?5MsE8kV?RD7-uZ-W`+4l3c zoH18Nt+_6qXVGoC*e%PD+Bos};TAT`I79Oo!wX~7#LTb8>2)<2mISqLHr;qI@(AA% zNYR+%C%Y)qp#mgWr0a2=-FTvEC9N?pkgfLcE+k6TQ%>uaKpau|t4>&`b z&INi>RHVTfp^4;$v~f67fvYDyi}4+mqI&)@6f@J@*P|7t%e!>@YNZFmV(PO-_VSqW z&|bp&zOK-#kF7sUFP{2~xPzm`QsEawpauO=$;L=!ApWk74ShlOOAB-WGd++GONC{6 zZ?yMx=Od;*4_&ZTJ)5WVDQi{??UdNX;Yo2n9@DmFRg7pOPrUt2Nl?L!n@kpN%9K?Z zQj4(Kp!0oA4tLLSRBeVn@?+nH@FMRlch)F+g4$P1hI&6sa^VWnqWJOXs`a&rX}09{ zxarmi-`MyJd67@@TCpc_yUJE(&6^Zz)oo4@<7g@H#gbiZ=-0w45rnO|hPGVPJ3ku!3LV)7QSf|6~H_$U0epYB}mp2+{}|UzNK`v)jWK6P{QfOw{ zr03RPZIXmN7~GhJt$F|dDEsTMD!Z+X8y1mHMY@rY4y8*vrMr=mmhLX;E&&mxyStI@ zmIf(d5fY1zcjA5Tz3=@z&-Weg`;Q(TfQxlqW6e3QbDZOMF0Wi7?bU9JJ{Si7qRj9= zJoLpDCWHAUFyp8Flm6Mt)6m0@zk1Z4Czb0-^U9rzf4DjoAi&p^GCd!PD3f2f|9GzD zyfCxFR%Ev2=+efznoXn8hF9*5RO+ob$74?4-Ga4!>3R3d&Ai-D@JBnFKU%SmFBo7{ z?uo!Rp(j!rA$J@=$r%Gv&(_3%ddtz>jZfo5_5|OTa0SS>ZbG8F=CxLH*?o_})|CTD z!yd!7mCpNG+-FaEJl{ZmCeSYLM9F>Z-f%^H$2!;e{-FXH1E`|~!qQxi&1k+GW)X&T zOT-v92O0H#SPU&xrzupTY8XMP{(!tM^+F;HWf9e=T;*fK8fP#CW=Iebx{StSAso`S z$WO0R-(v8MNkpxIC&a+OeYX9j0Z3VNu3E{~;Lbm1O~3&z%`}7k-9^q3b9HeVxopXo zAUH-wA8StW^&ps_H1f)C2GVO=$Yb*k?uX{{+?F(q7Pi&L9qsdcHdPID1o~cl#4A>O zZ~lcy6y*15b%k z%o7cvlXwZ@Xyt`8Q%T$_)+oGH_1PVVA+-!Dp^g(RwO2}X3stVc;PeYc>` zS99;diNQkvqV#+JteX*3(!Zu$#{8|2+y5JG=9ia)KlnEMS|cf%;EdvJ#%Zk8=2V5< zy7*-s`EQq10pA^Rk>LC)p0CJkOARRLBW_qrmq=6lI}eVce{`>y?} zJNeBnF8$crH$5UMgaFReSu2wL)HVsZ6;u#)v0yip%%n?a1mLd|DQHQ~l}x?xYKT=H z=QeH?R}WJjf&$e9U*QzhLrxu<;oc1`7am4E zUV|tN&5#+HbV@qcE_Ajk(>EoUT1)7IGZNY3koIu5F*X+?);TR2oEHwPh-|NPN#4AN z9%S6ip${>mhg;E#l8NaO;Z#}+&;DYkl}ReVDe`o`&_4>{ z4-e7$s+eRmb+v0vb+8msPoi7wKNxMnfcFL%Z9{y%eVwb_nEDThw$2ZLKX(i>#bb)5bP`)) z@VLSsCZ^;MqU{Ypv^gDtvAe*#Ctn(wZS0=;+9>^o5nY}cj^BKpq}`fLv%kzeYuT2I?hR7oFU zX6|XRNeo@61xOeZBH~gv=n`nQv7~L@Q%g19Yn4nKbSXCQD`9x#_l(l zC;hh< z5&yFAS&-~e2pZwmC2IUKQ5w7>7}OBJTrjL-wvj4&B>0oW?2ZQbMRuY2dL?*8@9aupSQhizZJLY#Jdec{ zpUiK+I&wDZrChtn9xowb9NfR1`F6efy`m;NE6B$3SC45B6a|7CuQ0*-gY)=zEi8VRD%6F0D8iu;=1W+VRCToz5ao`Uj}rXXYVr|wc5&}hh@%Q*!NSo= zZ7RVPk!vW#1OftF^AxwrMyz7Ln^iNc{xo1SXn*w3dzARHCMsdD+!=&22||R8xt|a) z_1!VG|H#&euwkGM!q7Sx0(btqlTz5{;h)~0`J3uo9Rk>?^wPDe&Er`kJ%YGqknG6S%8GI{|{14if_nJk5A_vrn0ONpv0Mm9pTHSizeH6@Q^*S+yCChY|}H>E!J}- z4uCRSeGKS(MPZYz^&L`=zG8FJA{lAm`PuOoKgJYZ^h}$`3J^~o^^tzWwBIKHwHolh~uq-12UmVOF4M8&HQD+dofr^v(7(Wob^F6z(+=cpSISch4uCrt>RrrnrsyhUG3;Gt8~mKjHtJu{B1U54XlUYf(1qwWMYv z%3u7@MI1L71G5-!+oBQ{1*uW?;;eFZ7o z0h4lxC8&6-wuj3tpPhvFM82;@a!dC1{y@u34FXU|3gHxN1`UPW(nc8b&G%w~EL{r@ z#o8OQ5`%_+4ev)lKT(;o(igVZ|bfuSL_{5`Mh-O3w)G zyJ4(9s_m>0k3dguxp^QO;SInV9{pYo5d27Jtbd6^Y-vdWx-!gJ(-oGpEH=(iSR3(Q z@}##q)v^e|m&VI@%@D4Z>qbl*aYSX1XubG-x6UyNPiJ z-EQ$UR?CQeq_9vJ`LHbIvn%dNWPGe&^&mb1hQXxSXruv4?r~Lkqt*>9!RO81B&knx z$wa7O14-N2QT)fA%srFL>#c$NDc^$270LwW2;Mwuy**nv7Q!6_1f8V}SkB|~6`z%p zBvQzmd_Z!5r-4DtGDC~lF+^_9_oI6MV>AbZV9l10Y}M#!Jt4abp3IAMV5VxP>?XT+ z)%0~M<<4k&<1%v1E9kd$maZU{qFe5lC@}u;1ojuJEVek=z!c~=AeJVqxsgBkMRD>( z)MSfYM>EG~SrV+2m+QSMc{$;Y6A!$%X%OxJ1`H65;L5#^mT)=$9CACz_E|cn6lRh9 ztqlsc5~tfctN&9Id8_^=vs#FM;l@}u!g+m#5L{540^X;J(h}rLsG`h{X zLobb6uGe^3?kz=JtnXi>KhgPMz7Hm=bQkGwS8|H`(I-1 zDh1GW?)m@SV#b42d4!Kj+jV%|U2bn$ zw#^ogw|UQtVjF%0Lfr(g+M4f7k zbbv4B&|qHTo2b%b)+$UqRD-#79;BY|E3k=Aj$rR^o2)T{r>~n^$IPTPGWnM zrcK1_U)V0QI^(A_t<5$ol#gp#K%{QyiB-8)y6Lj#$=k5d&?7pBtEjlRQiZ8@GxLWf z={%ODg=GIn(rS&)>FPGEX&=gS!A)hYHs`>XXv+cp9f=5=kM3U3M`s(QMLpcynuRVD zcyGu#VPx^^xFo2?ma5CaZ|*P;<|y$tHm#m4)mGTa2iRDtK1xX#OY=L=xk?iO3M=3n zs~jmoR90Tz2b~dYzK2eMnSIr1Tq#q_FDkI$VG6#WQx1Nq&GBpnMllGP(%ZHlTA*p> z9{fk61_F}INT5Hc0l6D^*I( zLVKy5JiEb${f=P#YYT)Q8tO5l5Sj^5s>l?`{dy~yEz~Ao#b6!t?0-~Gky^>8=aOH% zuFo8S+Y-bH&ECww$f{!^XBqd$>G|)0D5->Z;G48gHqlUCsc0s87EyuF-v&+zS{=DMHv|3qKa&!rzRK$6}dQPw@N@Bkw9 zQG+SmV;UjTbK+F^nS%HxlcK}F6zt-9rt~;y|DbN(H;I|akH5l{nj1_2j-tTAi&))B zCk!>GI&Na0$}^QLvZ!HYB!@~j$#r_zoK51BwgGgs$b6{C=vQN0WXv;|{4f?{J?>DJ zsRr!7y)b{DbUwTS4ZOeayRTmms#7smsM%5zA%tCOm}m0UU;7$fxt=}s^V7KgvO(`J z_{wcx%y%;xA1cHu7kRE?v`tI=ACY}?*VtiCY^b^iRli~>d25t z6z8d<|Iy|RtWPFg-YoH)l#8>6h^eKfRH*kQjbcrFVH$|X!iuHr`@b~sP9y;n-8*5%Or!R^9Ey!?bQyrP0|XLp*i5mIVXy({hvr$t%y7s{%xRTQ}~ zjo)U~!LXjT>X|*`R)r6J5Aq}xl``=C1Klw6D#EXV%oa#<;FS9f+@&dk?)-z6rV*;(U57P z77WzAy+0d)l?$W0ou?4`{wT~JM$-ux~`WT!gmWw4_cRAO?FMmW^QlC9yF>>t1hum=gGI6 z&!nO}J9kYdS~7?8RVI*)y$CykS@?tl`-{Wy7aQI{U>2hi))sn^KN3s(0FmtHzA00O zoSV1;G-KR0WA_$G#WvN*KE$Ho z^07SLJ=$A>{8Rflocp-M`j+?w&K%Bm`|E6j$Ef9%PbH(v zlvWP744yrEHkG`{dkk3X1Hae0BCQ|M$fi?{A8;6R*i3GIUkW}Kuw8C;AAqOXz*<>?oL9UmkZL z6j-wuzhqZ0nlhCwQPN*R331n+y7tF1`a-U}!Z^{~fUPxMWQJda*h=F#op_ub$4n|) zos%$_)v^P%r(iLTr?ytqufLtHs8^e>&7gOJL`?*vs$-sPL7DGM)EJjtf{wazC1lPI zX4b{Ry<>R~_jc0RP1}<%6GG99I~Jn^GI-o1eNtZ#aer>^ZTT3@m`1Zx_}M(3$cMqu zx4@zl-7~!iQUFW3jm;PQv}2_TD}B~5`wi$|ZbWh}k1f4_AhLrFZWRes&|KR+(E;`r$7$iPE2Dr*i>B z?LjFzhz*EW9CY9XI23GQ^(=*khyDB?J(9fP_*-9H6i8LrRPqL=*z0eI93M!!{z>^-?v>Oq3)%C>EIwLOUl;;MvymS{+t}>RJDJ;V=<8Q>B9|MiQow|8)764>#g=qr)VYm*?QG2|eEVKvekdM#EqNM` zQxTJgi&BxAf3*$2gkV&-HkHq{w!imc#k!~4VM}}K&x%K_d$>dUWQ4QPr%0xIKgM-hbj!2x;(0@W^kzbaKPN!)?DC zs&g)xo+Smb_Bf^UbL~&E;@U%Zpq(%xroh$Gdg^mUTXUUJKq>H}R#f_>2+rQCO4wrT z!R!93nw9?Ione!!4Gb@J?25(j-|J3K*8(he576=_*!*m)esCgQ`<<|nVBoQ|^IJ+T z`c2GjRPo?lSr@5Z`7*g7e)U!r{M3#?dP^qLTJB8*_hQpCtki#=-4?WF!jiv9sRboO zUPCFmLDemLYQQHFQf|wCPOe~%oQCa^_qMIHB5D|~P?<;lf-9@>nm<Xuew=QTnP$s4c-x*b8@(*=>c{4{G_$_zbnk@*EJijn6PLd2rvJ zm?h}7Rokzq^)FTAmd4R7om+Ez?xv{VFN$6<5N@Y!Pgi7Qs|lmU;I+Jn{?o0 zyklco%v0`u&%s}+79_@X)eUV)J9c8&6OuR5)#3v+V%d0^lv*))*YlY;c zWS{0uM?aSwKCUpIq-IPQ>|$t8zBb4mq}Cvm*0jThfiD70AdrtomHOH|&ZcX6P$B0a zg>=Cfw(7?>35;GMKLgJ|3Ng`K*^1AHE6j16WvV+*4El(Qro`!o=%6?C^Lfk97@@WK z=iSJeNsL&uTCbh-pq!u99u1zlb7L>WWL4ibY>R`26S;Pyqf0w>ccJQfj+y*m$H9_^ zj#q&Tc0$*>^b7dckz&G$+L^o_IzF*9q*cPfZDG}C05_0a629B^AE6_#TeCJ@naA2h?gPnu~!3Lu) z7=zG0V!;5Mk(h=$DkWk3N`uVUCKU2Fk~MDMO#I6iC&ZSh%&!^RI;7sN-rCL-r_K<& zERvVCUKM&T0v7OjF}WRrw1f-MbVrN>1Fa*LXq}KRQ_w=5?RgEiCi{oP11+w)X&quN z@(Z5kBl}|?{Bwv{7;k$eeL0R-t0YHs)d189ZsxU`ff&UVmF*RkvbOVYnKeCTqWY)C zI}-)#x_+Q!c!mF2G6h&Gv+Fw6D?GYtOoz-O@T%j}J2>X|5fhN)xF{`b4JZvX28Ok8U~zRM2(fkVsPs|Kce>+g2R0K(^ws4ZR-4qaocuuT%8g*#f7MFB z5w?Ow6c({fO~e$5$(MQpGYdj?i-cASGZe2B#h$PtSULYxCPHPxcA<#7Nx^%o+f_GU zaLN`7$0==KFz8FC*?ow2G#yG=OL>8r&}bxE5MQu#4^3*?9O4($dx6(4s7@0zmYua{c7k)%s{J@wUnF25}hG*)nfBkjjxy_R_w zc8o6+`&&Fg<%%FxdTACy+lh<6Rn>$9>qt(*$pp2)mW??H zBV}pP<1*-j#~|7Bh#FhG6gdoMZXFX;2zY9xO+m#N%%I^LWPeG8(zMf?l@y*(o$#F( z|Kum$TpN$mV*X@X#!N@d5+ml9!PSulUg)|xscih_JsJ@dUl%oGmWU}YI(=qz^u}8p z!SLYG;H`V?PxNvY4mF)iF5=QGTtw3_aO&FOP4w4b;?e0OhI`7FlYba0tbZqhb6Y7_XEYz*^=XiBy;1T=j~QZDVFfZ%u41CI zr-^!{)G9XnZ2G6Og46jjwM?Qdipec+;m^GzL-Dq4iuw{K=)Hq-Z8kX2GHuFcC zrc>tA>t%i#u7X)CYcP@N9bvvUzegYE8by!7#r@-Se?eV3iDBoJA=eoBBaz^E0$IvZ zQ60c~9LVOTktUPUdzGc%0uazdt2JnU=M7`rsbwUnBM3yG z-<7g5`A?Fq^-jWK@O^2k{_M7ef7^xb)0BcMc*c_dl4KnPJ|O&E^OH0M>b_HVYJdI~QTp+95&f_FO?W;o&^Ym2_RjWyGnMN3!}ZJsA$p;3GUo8Faw4#HoAAb%-4>WTP{_a7v-!yJ z>e^xVn>~-JzRKZwp(XRJpyhP^`&*RT1L2QNC^JO!vIv@)!zB6JaEOg8wbO#VF4HcQ z-|;jt+ndjCw%Pf;ao&7MJUpQ>8MX6iOL#cE8z;Hghp2Wu!80&<1t_rvgNlY0AW?TO z`AZroA~bxtJ}xAlpm?YH+T6Ek10{9Kg=K68zHPvhb~)oF)PR}7wYY<@c>!W&PMW>~ z;32bR*TVhNfObd&)-Yj*_ZuyS|!`X0`k?&n57!}nKsi47@V^;ysMS$*p<3_ zrb&XglhZPY8IKB66r7-5C5KL~HQcPwC>~l|Wykk{ypO0`)AP40x{9$^ipK^cT}=ZS z_No=?8eoeJ%FG`7;rp}XaYR-$eqXf-{Z;wVCbY`}HNIuAnDkz5;@zWHgMnfc?T_eB zSllR-)8gsZl4Y&ux|vh;n4fbE7uCIdb5_934;dO6V>U@wSn2kCSdPq>_i9^r9SNW4 zerqkl8vm<&G9=Dr3IuE=Lzl~>)@*9LTrj>b!H;8qaH(WxcB_tedhN283)i@dEHty!qMyiwg5_Ce7XLpgNclr2vU3e)%KTR}fl-kV;b?1rA%yt2 zxKo&etzYBc%z09O5WPml{-##YtNan?hgC$&8#dg8-gUJxN-wgP9ZOD3dZ;(NDBU1h zqO6GWDMwSs3gpg_=;Bvc0I7b*-SXS)ZKy#X3*M_%1O+XhoOU$2y^81d1o_516^DT& z3H*5V*Q7mJ_I2x}0oz20kJSC+5#-@j^d6#MufjmMh^WPXt{*G7e%jTnmsHAxedyju zDSFH;#yvb$uTEv~1H41MX&K29D(TCQ_EvcBR>V4X+W18P_Fr$z8e{ z)Hp8e+|FT4dF@A6qEJcSRQg&-NUtZ=Y|#02Csg*UK1E>65Adp@g#Y<=#89Mvd>K<%QvAr@#a_|R1!XsfTsqAy=zYUlCItCkN2$`hchc83R4F; zkQ$0hyblt~lX*+ZM$xbca?)XtsCSfH^^qq#^sZw%JG0@`W~0=D4OP8M@mKcR8=!PR=Omp^0`z{%jhLgB^Pw;If>>J(o2Gq^lFO5Ai7C?n z!I+}5RxN@18;6zGsC(zYh2Hm;bBPk1OPzdw(3> z#oOzEJC@M)Ijx>fw&~k*q91~a*EBzW@V{`_4ohI9KW>Mm$dL{`g(T1CI?L~8=HFt~ z_)x?ie7?e)+9un5RsMX#=(pW4d%ah!hs8*iQVtib)+xQyL?yf_39obf{Pt*$&pr0l z<$Mi7wrJ?G{V4x^Y-dnYf4(B&=_xQ-F*@x={4POogw$#js+0cYWW;F@Cw_g!=N|NN z1`vK9kD3UElcl!o_5NH_O6e3$Dl``7jZ>Qj%=Q9^9XW7V=O5{AJ1ZZR$Ko)O(c9^WPGz zDUb%}+<~Gn&|@N?9vw_z7F0NBDvOrQY<(-}vy(G^<+jo~2tBdigzr<=XB>~lWxi7_ zZK1>y}wNOu_B9-(h6{Sbsl)!#W%m&RcooXSZ>jw{ZoG zEylrhFtJzvfA8%Nn3?gFct^O=JFg;2Zv!geb;!4o*FA_M;wx7R?oo~R^6);^5A`GF zbJtB{r+2F%PE{=(+7JDh1B%b&A-7o0uU!vP#mv{Oh*9?}2UMqr!7 zaPQ#d&U=t=^5in&_}q^BwuaN7Cj{&k`OM{(P(MS9s-2~#Isw|wdZ&ZMInE^kzjjx4 zQRxjbl?A}UJ`E*6U~Y$rnANWQeq|2_i|}*sLZIR93X=1#JFF78=J<(CjzQi@<)Y`w z@Nuu;onrE0Fl?5p-(!j<=LT&h#iqQoQ8xalrB_PDDSdl$Ge#<|1$7GgQ01mPv>%&G z%GWd&_o75ixX&GJzw0Z<`qO%~z+=A2z=d%HSH6 znX6nGI2nze3bvXpHL{;pC3fU)Mk{5A!mH3@%4hcThii+$rZ2`~ES1!A(Z$uQ8%fma zd1N^ybn=ns{RflQZ#?g<0s=|ZfoIuqwDf-FL2zGqfUz{J1ru(7y!2yUTwjnJpp+|@ zK?m%pvp(NQy)Uvplk7q}{YY)O+MYA(=_J32#(yO)FF|{nd3Q=f3rswvvh7BQ+YD(6 z3-2gYe4VaNZLg3McEa)0IgY;B0cgkpb>zZE29J;0v7@^%2kD)dU>R$6s?Nftxn{X# z2@!V&gHPzSLhYSt#tMsV%|&jd)u`#Zz1&xG4Fh3k>R~7OnN@xv-l2i|@v{EflF7xY z??kC4o!%E~A?5#m+M3g$1-k?6&XEk+{BjRR?xzpu zr0X}1HXx* zmR=K!t^&5~V@xSeD%LN%JKINsgTJ;VAkHe18O=8?fxhM&g5tY7>w1JV*G%_l+(>8c zvG$&$cK8=R-n1PALfZ71i`LZ!hfH6YK*=C2j&Zs-2BvO#k`HI;7cpx3PSZf(l9H5E z(6>MA`xY%g*A;@nSqqc?#ZF1QX(48={%tar3*quLfTCGA!2PxbNm8CPYakVC7wdOl z>{{&GFB03Q|vlx9WeYR{ha^;ltvD`wYsOKiV;m{_vL&*s$y` zjTH1_D2CItdrI~AEte5nA(f1OOXI(9r#;I5mo%>aA8EX3biuv({W!Dzb_Rb&RSznQ z>hQEsfP)#o)7a`EQM94x@AJ(k+nRW^3Lw?V4Uj<`>ym7i8h1$F(8y`18$Oa%2HJj2 zY6ssL#;!scZdB7jqs+s+4&db$2^>DBQ;}4Enw_2f{$#q<%fk&oprA&1_^?^E-A^7D zgUHl`ONaT(z~9Ibq$iC`<`URAvdM%hDh6$fhnb*0_bi#+af z<8zDlcuK0xAwQumo%goUvohR{Z7{H*tMH-W@s~2-N52D(;$-~ZNTLf1kK!Ln^lmr3^*`V~z`JONk{ zC9fYu`yjG$)biY^Op25Fp?KvG`TluwOA`&Kp*s;>x#b;`lq;K%^G&%3)90rACV1+@ zTBZ-w!*X$0*Ydvx3alqg z<@H#+z!J136Ewpnt-(}Z@oS5zK3i|!^+#J)Kgtb37xQ^4pIhbf2)W3R7j*hyr^t@w z-L9>;cx0vYPsLQYse}H_B@`AlYPGW39*uTaOvLUUUON8igMvQ?sr(HwIIxzYAk`{+ zp1TRHaAtE z?KIaEuT}Fb+X=#bWv8=9ce$|g>~uYfcze*}@U7zr0am&q!dq{ru^)mqYWV^1@JNE? zApWW_5(^b0Vl0VK`*ej16?p^%ChTh!O(2}C4p972#B0nad(Mf#g8~tY@!2K(s>}t* zu{q}FO#F_pko1A9y)y+hjU(cypDA9z;mXCjdhAfpuur+tZGH2OExAK=vLgUNMk&t8 zWE>P`$zq+Vy-P7|znQ$$POI(UnsRUUz$*2G*&NpjUs=>XWz93KKG=H?8Ff9eMk2{* zLrs>t04p8A37V8dBMBGtu6Kn3*ZX~H{L%b+8pZLN)I-E-a99SuiQFlCu?1*4=55al z8C(9;qOXDgeakXDF7?yT1%K^N3~hl+uiOgKkwN=~|aaw-;2rzn6FFK0?dCx-=IRLGeZ)xx&k5uioM#{l{DmotiFP z8sPuQ4zhPkp*mOf|4TRbe)6+WEc6*w_J1qqGDMU%AIP=`ljW0}c}$}3F_Et=7Qc!W zOim5%c1YMw%k6)j=ahNL9>R^{^7M(zO2@PS9*r-(Z#`S+GY{W5*BXy2r>Td+Y-si% z?>IEH%Dck*ExN?4gA zAmp@}5*$lk&;O!AvgB@UK(h5NTOT2J76kznzqTK2>2N4{GK_ z?+wMS)9O)uoRGn! zRo(Z4!(!@X!~Na0mvE+e!thUvz<5wXJyY->Fu%2(jS6bhL;x71v=&Hc`y_L!ijlr* z$CnMKGWOGgsFZG=@8XM->eFcri#kuR-!|blIx|12p}pE&>7OEpJH;L}z~waT`)h?m z3U2pRR3Q!Eh}|_($PB=4UX!L6`lOo}Q9(?>>NbbJ5W`)8Hg~Yp@6SpMf{6Fg75^yg z?%xhCMKGYsJ={{!+rAyrMrbOCs|XRg15vEn;HXz=e-rtQ=aJxh1$JM1=M)Oh3IlFPJ2^#xjY!G+X>fj=%--=6^E(J#`74#ZlA(NEUuIJU?2Ezu|2V@vK`9^Ox9;z>xw6aOq@Zfi zMeJ?n50ZB z2!8dZ;FJ!xQiN{T$N9y_zFlec$^oV>fq-CGVt+-?-yH}!Koq_Df&k9psP&Eb5n45( zpV+G+hJw-5=9-Eu>!~N00)Iq$97qw%pqL_458WM9a3Y-)EMfH?;46{_{Ai(3v^i{c zkh?slxESf=S*VL1+AuMb4IZ)q>EK!3d@cua&{Std({>)FH{m!{r_-W3<`Jp`MFH^Q z2tHr^$VGuRt>Kmjyaw zFzPnW4VP%@1&G$%b_q!ZM#fz_1goNd9r*8VXy>j@jD?`s7q@l$-Yz3B=ESflQ~2Xr z1aJ3qNNH@eez|aVFys0#vxjg@l24meA?&)n&>R8Purphhxh$71pV3}!1zKev!i}}h z?n*VWnM1$nJ#Dfga!HqxTQ14O00PC?UacO&5~Jc-<_z!at=Ipmm;b1n!ZkUw%qB^7 zc0bQFcaAuI+t*_jILk8~ex7fe+JD%@5WyzK3O2Db8IF$Tw=V_PP|Gpxf4CGrNFlZj z3LR3E*_`cKi%u0jT-hVsh=FQo8abqgmv+&pr7RlJA#fZ~%P+rW5;FEW6Hkslbrt&M z{t*)?TCpX_g5e2h4biVcQ4RR31Tz><7KUfnEpl|KpGQx8rZ`5})q zVI7xVZ3x#CSUlv?*gV}<2RFb(`V`ganlBQ)rqa~S*E1}ip#yG)#h!sD($jV(&rA;w zno61=Kk~s=Zl((!JI$J5zhdFBSdk8#O!t0~IkZ;Djb25&R;Vj=3S#v?Y_yW$5(#D{ zIcS7fLW34%R>XSU*q;t%w#j^Wu*4I2&atEZlJ;j+{O#huQ1#8eokOyI0^qIH9r)pDJ;55KZ zp2?c-Te~0AKY*xVoTSn4!_16)yUtO`!^L;l@cw;SYz|EyH8cTn-xbPUh5k(3)lITHvy~E>|TT3{nROSNSic!k1QVqwcmBv#sh&@m8P2pZJTV*3=B!5 zh^vHis()Xft(a(iGiR%wS0LtrQIX8r-M!XYf&8{`_-VRwgBZgCueP{u*N8&8^?&R8 zxX7pzQJ6!F=R87Munc9FLVxSFb5GLA0(2RlGOd;y0mHc>CBfgb#;HL`2E+(Y~~G0vV*yS5)Itt8#c=-!eqQXhVw2Q(?< zVC!`BMrRrL_)t%rxU(4J5#eu}B+Qtx*LNn&2;6eiBL(pb1f=ozVj!p}hzXZ(dfyyL z+XkqJ-)Q+=eqSH;{;_~@KIOrFmJL3f z!F5Pt;>~Ea%KLf>wzvZ_-E^lirW5P>Zx}A9;_LbkzFh5mZ^d#~I5YTSxpKtc3p;N& zD-@xbIBm@NXPZv%c@z%x?N8wN6pDV46;{;PVG}I$HRRuZ4xqS0K|ezuyeDaA6ZPt& zucW8{*1{==Pj{yWZHEwrD`EdBzt4!YC3p#Ob|@gTFRRKu7%{jbVQ|O;F@@hf{f`7tllNl!84fy-YW$auE=DJ3957VmvmJd0%!RV z{kL~Y3Ce%V&JX{`GpvVe(}l$ynbS0k1Is|yVt-#Bsd4~SP%-WI+Ec`jzrM|RnfI?3 z8Z~N!6c!8znsY0sAx+xna$x0d%36b8?eWoHuTCK2l2)RDRu0_mxGaouwL_RTegZ~4 z5xzU>06SF@S7EdbFU#3~yUvl&%6rD@^F&#b;=IC@3c6H*0h=o}k%Uj+ej$SIQlQxG z_>NIp{_4kV1aK}za8Cm#T{ zfI$$a<8upD0?KRKY!Y=Lsg z?<(tsr*WLN66O=eDc*X;Ka%5U6{Wv??^@++5@6mAj7D}VTEr*& z)jXrN1d3@TWl5Z|I=fZgwu>{2U(caT$5 zo8&QAbB0o0j)9m*H+3uf*?UDWo2>?$c}ZM=*b>f`)MsVr5c=8XhcI2+gi_Dvy3ZKg zDUG%(R+f7nHyw4PGNBUlzj_bOlfR}P|Gj?j^%p2pxE+7ATDIaZQxw^lw-mcoOj-C( z`M)yM8krJDB>x`q&+Dw@%WghF*`L8%hYB!2f<8dC-27x&8VhzUyoJ3@fVzEY+jd)v z-uS!Bq2&TMD~?wIc^U`MMBPrd5^0rle{BQr({veN%roEKfSmd1M9tYk=0h+gJX=P< zFvZhle)_9Hsb<$3Xt*c#*ZX-u%V2Nic%o6()+*AnWlpQK5m9Z`z+vE4w?|qZ2~XXZ zaZVg_Gn<)sO?MC@)Fc=moA*f?%rzdj4HK7;&au}@wpxzLY{T)x&WMOHx%fh_AORa? zKKt~*Vx1gE)fOk6ct?n7FgG}pemkCs)0i*Sek^C$y6DD}zrL(hDjTV`TW?!7Yim)J>{I6Apo4?^Q*|m z%lcvV1AnjDN0hwIAFuqMLVGFVV#tn1cqxFI%H~<$@P90!L;qNb)fnJ3Kq!UcN~HSE zSU&Ja?S|azJ8_-vsq!$7Y0 z+10FI@#I(8@%ZlxU#LRtAhChd9+%SHMQ_=Z-6t~WE*dhvK~~zDb)8pMRL(7Z7=I=n?d=t>cP-dbzmtScj~hW#2yT2 zd@uCW4`VZiOFdG#ivCU2I}EjAJKq#L!7` z3Y5gMvCI8gy}+KC@?TpWayb}-qgU7Ws)-;K)N@%8d4CF6A_JhE``)Un5|D+G zocHHyNxjgaiqJ>Z$+ms>;FUmq=v*NVh> za+7kmb&eW(t+BL}f7%c3&hBfa-I-f8WZVO~+v{iy z#BvhXFL|E>nY} ze#^2JYS5@miX!IStp$%iGGL678Uz814Ss!$0^1j&ZT1qn*PwH}A*DEYvzu3IU1Af6 zSgw$0GOFKj3}ZfKAk*%i-ia~aL^}V1)Q1%Jw^5^y65+nc=fXA(0yN37Nd-UfjCSsx z--pf6ezkJn)31Mbtj|A8Y`}lMSl5UQX*#4vD5l2=TpxLEkX2&Qg5&O)uRedjfcvw) z2~+r7U|EZJ@Xlq*vB)P*)dZ??i__|+Xc81guS^IpbuW_2aRnuNB7yk%=h+p4X@@YK zvUVB!+ib@gHKl4%ZMTYP8hf7{wF!#}iB=01vt-Gkb)tAlzH;u8^iQU6<_;uWGHB9DT0tg8MKUT{%kV;xdnvaMES}FYm37G zOjG2sI6rUPUmNjxWjZw0nQ`NOwr6f-xzt#*2p-%8$YncqVf8_TP8&4sF|e`(mWH~v z)Yo%?1<7CU9%wX9X-h3Ez@O9zPoW@8mDGeL>wbkuyxlBs-Qcl}#A zE!!r%9cD=)!-k^aL~qLIUDm>MeS)~XZr0>3g2QggJC%9W9BERgMYJ(c!Gm*P%}6&g z=>GbFAsD&5mDkj%qUcylrr<1?%8^Di2l)tF-%RC2y3cX$)#LrXb z6=0mUZ>>E$Gcd+$%H$j@+g-FfgbO9-$sFH)zq>u$_SreAS3o<1?oQoyWguur)zP)5 zcREx2=(_&t?L#|2TjwGzCznJelk~(j50uz$?a*HikdI~s$`aX%FCcdBzTPjL$^M^b z**YYL5Or{2%f@aG=gcyvqsOVnyl^`etPh;HnlLH$Iz2Vc4t?&M{&GzsGx7OsCaOCo zi}CedXdw1u8W`UB+?`}L@(iJu%R*3d@L!n@EV*RHxI=3PrwXc0s|~ww3awMhNME#H zon&J4XG>KXueMXg3EqpPa@jdQ)O7R$yq84ZJ{3kGX*cod$p!5H)7f{1HMOl<6Okfa zX`-~Sk>0nWh@v1UNRbkHi-21NX`zH75K2IrA`p5J1f)q*Izj|N0Tq_Riz%>-QJfqEO8!J2#-o+H1c3V5T+U&zCKEFjyc$UCYWUF?r z?w6{yodzu3nd*-I9W9=%zHy4Wqr0Q%?rxxzXUwT`lqgkyP^nD6KfwE>6Ze;~%cMfv zcb{hnhw+PiMVrYur=JB(`3J8it?PWDL6RfUES3zi%wt8N`>9K1^wWRUjuSWMmGos6 z`d*`XIazr0pr;4&%O1orJLd~D~yDxKJ$=M z={nE`OiXpE(}-Xb6$l^}I3&PrghWk0LaU+Y?LUE`7hs3}#u1lP!wf>cSWc92$k7o7OfA__Hj# za1i-v=BMxQ?hy$FpT~H&`OvNF{aD$8poQV|=*@J-FK1Rmn~l_r1s!z&>W|v*3e!MX z=zg)&qr9qsv->cohRXDkja}QpK7S<7a(!<3h*d>2TjQ&jV?w@MTGA#t=@tQQik9W* zk~r-!Q%90Pi|~B`uXC9U;-D<%w?VP{E)TNSHUHanJ+MYQNwL8Oh1o~f(@__d(DidT zzP&Jv6mX|U@^GJbvcPUc+2iuP?fE!AL~#W=hKIZ19L$hH48n8F_%C-;=3PtM(ll3M z?Hsx@y4+_mAj8hvQM}M2OZ!W3vsU}qz2_ehe38cbHAV2U#X=XyFC2wsTESh+WBnH< z@DW~*7xLQ>I>@Fbw%Pb$MJiTGiQq%m`|@GK)btjZX-1Z^@;}BB1|mc0I2}0kPg-|e z4$z4uIj$pPqDd#=T+Np?z!h?@ZyCajp%?j(%7w>lfLSfJaSq=(;e ziyc{&R#O10@!NM4o%}@no(io7Ggw}kN>9S|V7o1l|JSj1T4mE>C}Xf8L+v&uCrpJZ(cP;M-C&w4meOy6j*r4v{7N4pBwE}B}cVqz2EsU8jK{HXR zR+tc|xSFQqH*{HHF<<+sOsdMI4}p}zXU?GcEPQI+Mb&gNE<77+p~nlUJc1l9R>T;C zivB+?%O%(F1k^+-8}S@=G+UqRdXoR?FF=t-0AIWlgl_Pu05zbrGZS}fCNI?~76cI# zzsXl?r^;)K!SeZ$pd$JRqEk}Np--a8Q@G#4EqZfMydzP%v>FQ3PR&3e^*jgsM1Cs{ zk)2RJW1;03F~p%aEMRZ0%8;s3BC|Exe*5*}I&*fELTS&<666T2TH2S^v#?uzpn0Q9`!1B&&^#^}Ry$dG0}i$yKu%$#EX~>;^Azz-129(xD-w0iV-eQR zJ*kXhx0kIHMR*(FsvZq-ESLVeBpB`tUv5rQ z68hCu_Tm;hg$Ao7pn}~Yl2#2D>`>^ck7TrHYYM=Bg5_}IcmGOoRRH%;Bjb{{9gv~g z^#)`!9P@!g3#cDH znh7Si-vM!qnWr~+zhF!O)aoOSLenwAtm(t7Hn}&Yoax$amiarrZT7R~>OC(2dTO%7 zfJhzT8KYSF^(d24zA!VXJ`mES%_Spt_={B(n~PkZTgN$?zo@+n75wI!S_Vi>VGL!! z$RPnzb4=DWi8HL7`*)9NhCX=zm}imZ7a0u`gBW{k<-MR~L^o}bq(x``3101T>e zA8mfDe&j;gO-Z4jHuj)DO{T3OdqwhUq=!sX#_g<&)^);FnlDTpokRf<&y0HDV2477oEffsN*tob& z^hqSD(tSaH^uK#%0H^TzD3WxKXv~LD-*EN~_!0WczbRQ_#aott?Z{-WLER z%X&xY>Dfg1U0q#4*)p#7%qvaOBWC3`Xr?ljY)I56kNIxsn@=8#xTkkByxVGrV&Ll1 zl}TngIE29GR$N}bXA258dya2A2L z+9w-t=sBXLg1bW*PtO41ujIc-nB|BwO{&C+`4O6gL_|Uol*{ct#Ky+Dv%s4C{kQ-w zAR|;v9A=SBq`66g;L{h34oe-UfLDch_JbfaO#baqw|1FXrM-T(>aqL1g&0^NOhZUJ zMG)xF#bv8S?tW{5;2Fx&9*j3y8Y6fV5bs9qR3-C@N|t{=aCVg%Y1THWsdEJK7iz>U z$oXts_h8`O(tclKgr_SeG1|Ivi3voTXkAO%uJp?#Ed?{SO?9bKZW=;Q0*w8MJU0B} zdbSfPdqi&(a8*SU_D&GjS~;4-z&tPYv>tT1z@$9eqRw4IUtd3mSbh#j+}aN3>ph=2 zAgpdJ0KKDrTZ62~%s4m{6J5YK!QYln7jUq1*KhNpb>lLFFb-x;u1`Z0O%>t%YV?5)3&%K)YiT1-JHL0m5FgylLFNr~6gKq(|x zKE4u-T<^In2C1ybL-SAmUiZ~2ZLeJ%J*hY|ssUS1%(!IPT)3_F6w$AC02E_sZ1dev z{7DK3CzAloA!|76SY?uhl~zn-LF-X9tDiQWvRv}oju98+(RQ+V{B)y76p>c>eA7(9 zRCMlonX3x>BVYpvq;MegEyTtmzP!*()OR0FG5isGVUgYXyvj(RjDmNyztW|HV}q{< zzc$=Qa$UA3QYr#gZ;tyMgC^f?9=UBO10iqN?K)l{Dw=4Enso&%6;U9;^>y?o;HebX z(_H`h?Hvz%6Ofu{v~<8?B>lh~h#}7QnLGDltnPFsU`Sk*@RE>|tVQ-9z2xKmXsP8zUOdagh=P5mVU zwy#NxntZ3X-ei}N{Ih72m!~ zaebf?0938d84KpOy36Ep^k9sfT<)O<*rr^&)4w-UQHw8}IWybPczUum(rEJ(R8Z~^ z#1Uago~!edWKhI^&Vm45G-)f+n)r?SCC`QkB8*yr+!gmk(gP}-7K$4Ov zio_d|(>t_K*}%Ugbif87a+p7Hm_>{PRIt2mw-h`ePw+Pyb9*gmf zwl+0)dX5-XKCJ!l==gvXQv8P)4O3cteEd8Q4M6Rd0Ue(8m z?~?|~tJ{uKTjO285k9cWWQkq>PLBJ|x_(z8%FG}@pyK`~AU2&XpE}!_*a>nOl9>Sq zwa!D0egt4YDLo~67@#)ca?FByv`+2-Lcn`>X>VfdHV6uYLSeAk!~&t7Y_NMG&Y1 z`Nz(hpA?ufPdhL)KxaVnVcxx!Axf|=I@iu+{8FE69<{^eGad)SEDGTBxp1XvnVtZl z&%`$*LD}T3HAMr9*E?2u=SNTp0>A7!j+nmQlsDIU=_i6yI>W6vnPrOHmHNdyXH>pw zeoDEPd$%hW->&rS^Qfq?Wv?;_0?em^Nu_pqCB;E0ajA4w?A9e&SSYh~YF$jAwZB5% zp6`{}QQv!?GMmIn?`cacU_{TGK-GAk@H1Waz%OrY)mrSA(kI#KS`adI>=h7FDtf#6-zK|f;=)hr*S2g<}^2-T?=CZaqj9OzFf|h zD#)c0v&zDFapTX@SQS)=OCbP^u7RbNkQe44Zdvy_3Q!=fv_U5QFAIMrhVnw!I>bxo zDY>{Wd37r|sDlkXeK8}YBb-%WXFW1aVcH35dMrrfr~z$eg4C!an$EA_6Q78&6YAk+ z?}>e{A5+$|Lk^0B61kLw$TKYPl>w!m%ceBI>cIk=^v>9_yqx!*Z%M6cm)mx?(3YLb zBd-=xB9zU+KRjmRgcNpndzvrDXA`PlKE*$NRuMb&dZDaD#Jc7?XDz-?sxDTPyVBuF zci@Tn?%R!PJ(uG%t4aovhbVZBU!E%6kN*?fti+#IXpW3uTa6>ztE0x=zMX$Q{I$Oe za%8PlAb-XiKv<#k23kLlja1Zin#qCjgfQjA`hUT|^X>I_1X0mPIm#1N>40YL{IepU zz}~pv*{^iekTx+b#2zGiQkimHz8~Sndc8TLSuA4%4CckorQ?>#z2&wytWoIT>Z^fA zDjbX_*mGQxjubcTYvsOd6EsBq8ps0X>3Jq?0$sfj)*LW8Ne50(tZ?ara3lzYZ^39ea7f_4cQ1NF7 zYLf|E!8)Ra(eln00XHPY$3)>rWDDUA$J%k<%{ zF<;IZ9)%=CE9M}koLYP{&pTUPK48Z-0Z4hQtZm!;9H=rKau$f528;onpPu2r_$;XC zIpRUye#xcCq})c%eO{Mjr`AnU!`#AQ1p*y{IaOJU_QUc|AMU`ZC316es^W@UxKz`x zWM0vU_!*6&^Hf(c538LbJvK3~PDQcAzWTk=-Zm<+5Uq8a>$6qQBJip_2Z=}Z zOeMc4#|bp2Rm{70s{0pCK{)cQ-qe9wQ>uQ+&cGPfGiB%G6b2IQPl0kQq4_1KP(4pU z4?R`Iim{Py+|E|oz7c&Y_kDA-G7l5Og2z0|7|qj*9>t(0!bG=9jambh7I)hAj60*^_gT;*;Lb@}^pmNXvoR*ziOr>Bf|hRQSZ zDNy*C#7yKxWKvpeY6b@JpHVy*H%o+riIziat! z?GkcBlD3Rpdeg|BBcG&BH}if%#X11?mIST$t)#ZqY#G0((#MHs~P z$zlqM><1vqSVt!TjP}LIr4Xl<8VgSlZLQN4ts0WO+8fNQ1XMDgY+ZhG6lOE26H=rv zO$V4rBERkSwvSvo?9yYJG)DY5MEUg`NMCYKlcvS5HN?#KuE({&D6(lL(>6(7vR#NS zgu_WLnBKYcjl9vAmu(tH9jkyA9S8gk0k6ulk%aYvnFv#d+(2G)*H-no*!Eej@-5$J zz^`8qRgyyNH@|%f_--Ch7SjQctKH|06Smg7ZU7I>&1avmWj@A>xl^-#yNrTZ#?zN-PGeKd;Spc22wv z_Am$H{C;4v8Wtr0$vLgC2O!biZOMH6L9#AiWlFzKl!X?LZ;*iW91jf_+?&;a1??2* z*{;~8bG4w(ul4gk+=0c_+3Z`1Qn}klBrYj=E^{U;yiC?g-3WA6@U{Br_8sf9pTsit zB4<`H;QI4;vpe8g_hp`^s>m4uMeW{k_|PU?Fimn%8oNPvWY+=;_MV_)4 zn2L+2_5%O?U9_Xa(CY%CTx8wXBA}-3kK|A_Ryk?_LLM)0FU^gO!v-OJ0ArB@fTMMc z!w#igs`|0{(mGFpl*>!wnUZ=IukEfDiq6!epnMQe%kNrq*lFSO(RdqVyFtc zp012JT5<^}(6|e~GgMTet&KfIpRpAIqigv{D2&#~l1j_*3$>w17Io%(OPVj3uZ$0O z8vTUb&v8Hn?@s>**Tar6A4Yd3N*d6p2ao*+)-;F$Zo)*$chgv6M-TV0|1<0QKcD#e z!D;wpbdE?wwn#(-%v9Uxs0n@x1-@j9i0R)sryzt?p;IS@o7vb4DpHDR%SKiqr0_E= zr^6nhg+iZ@C!U&}uMVJeYN%O%X40jy1q_YiY>!AFn>0y3i^To^CtILA_O@M3oBZEM zST&e%NSIvJgE7$0LDHCAtXwi5ibiCEa~R6z6GbBEgnWb}vbDe+VJJ*mnuKP8#IXy< z4cOR+o4p}(G+W1#vhxRwjN%L-9Q!BK^d?nTRrO#pp!s6h=H4S)?TOhUbj>?&{f^{` zh*g1x8lu#iz!8Gk$ZV00m&HOis7xe+Ux77ZUSOP-h6}MZcO>11B;5d|vKh`@4 zTpTot9SAd|`iFy8L~K^PxvBPJx-ysQD3COyP6Lk_;W*iVS`Q{1)zX7K5HD&kq=+OQ z?LusvSW-m_H!FPbjAM;CwKKi89-G}bT?+C`7U>tz9m{?%tciya8!0DR)b4ti9e@dT z*jLD5NgWw+vxwYa(2uP-|M+-EnAE?!E7jwNeMV|*0^#yA(s$x;cuZSH{;lFUQ6vD- zBt`Cg&-k8kyLz&_EjWBa4RCXb5i4~a?1FQN7kE>|5uF^V#C;mR9ue9kv43r-r zzDUx5Qgs6FgLEL8Lr%y}8^7R`tAK5eK(3B`qKG7q!AB!w;pB&)leE+NkTTLUdeaT% zF@c4=K@g~x@gb!1$yXtE*76ZiTGKei0EGA_2uBF}bgS+Z+CSo74=RWeg_=hQHUxcT zhyOm4)Fd3{1W4hKZ%6vy(F5`?e?P?AjN$NwkY9_)t;oX{et3)7`?7~G{qQoEB#A@i zpVx?g(R%o7hc{Y#4KhG~KOzHQH~)u5_(M1{bpL)%e18t5#NLy?H9aD`q3{>;`_DBA Tm|n<53h<|)rlVS-0uTBx67o(- diff --git a/Ghidra/Features/Base/src/main/help/help/topics/FunctionComparison/images/NavNextIcon.png b/Ghidra/Features/Base/src/main/help/help/topics/FunctionComparison/images/NavNextIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..ac8afe3df87fcbe5a60c7ee3de0cfeeb5aa95837 GIT binary patch literal 476 zcmV<20VDp2P) z-77d>!Fd*+OT8yBCO-}mYH{Ca-R#gFrPz4%MG`133Nl7B9cWf|b`jwp&G{|cW9SaYSZ z$bF!kCD)}Ry?{%7^RCNAbY3)|J>o#iaUIDSx&n z25qNyY{Wwd$AYl$R+Gk`3KZ0HSEGJ|rb8RnViHy!7g4)aNqT4s6w-~zu~&nohXn-h z=U`nmk({AhVB4vJbT^B|+bIZ=k>m{Bg6eev^YSESZ^psTsTJ3m z?<)gw9LMi3QC1TzMM|w`MI?EkND1-4)Kb!B*xc4R&W4d+({8Ov!V^4@CmxKkm~9tg zEi2_ud2{rwYv0YSV^8+r_2m2d_I+KS`(7QxaU4nslgWOdl$3cuk|cn^H=-y~z8DGy z$l>zABJ+d1mb*_4^#D`&$-PMq-Z)RKE72TSd|)1}MqI0W-@&wI+o^U5NlPg$Hc+k4iM0rBqfbs?v`hvUsZSchfEZ_5J<{K&hYe0F6 zavh<4%>>WgJcO7Vjm~U%!6X>L-@3rvI8kRcP@bSUV01OW9d&@co`zw(it+@_ zfd+dmoRKNaMy#kZ>nKmq9H^bBMvX-e{b(h0BNdbF|9LMJzLg{XWq{0ri{=x0ft{JYemE;zodkR~kVl5&u_8?*>59?mSVo#+tYvvYg z9+D77y|u0`W4XJ9uwjIUJ;Vr}){EzF@4XxtX4aTdH|X$X9>3rBeLs)iyn*3Dp%6+b zZtZ6trPTion3|p~4#;M+00$;P5OjT`Dhg1-X|TZk5v^%`M)Y?8xA&7P`nLe}+!erq zzXSE$)uQlp-$P_x$8;gImJ1K#+ecB?(*dZoBl=1FSAhkNNb9c4MK{g(;hr^|awd0qR?%H7}Bo z_qJnLEI))#9ESuEU9*}|z-~&CfH;2~s}*HfCBe(&`FUqeYjM7kd9?cs0TSbrh}Tvl zZ#j%#Byinw1h@Gr+~TXTZY^iEytM*xyA2ByZ_NXqoj(itbR)J&P=N$JI%&iFr~vW# zOS0xsIBH=%`AidH-mB&T68U&y^>)Zk2ZrsnEL`bPNSM@Q=PBl)Uef>t5@|b!xfic= uJqnTheqA_KB9%4<*pEsN(Eer|w0;A-L>3T_r0Tr@0000Compare Selected Functions

-

The Functions window has an icon () -on the tool bar to compare the functions whose rows are currently selected in the table. -

+

The Functions window has an icon ( which contains a star +) on the tool bar that allows users to compare +the functions currently selected in the table.

+

Note that selecting this +will always create a new comparison. If you have an existing comparison +and wish to add functions to it, you must initiate that directly from the +existing comparison window itself.

+

To create a new comparison:

    -
  1.  Select the functions you want to compare from the table in the Functions window. - You must select two or more rows in the table.
  2. -
  3. Right mouse click and select the - Compare Selected Functions option, OR select the - button on the tool bar.
  4. +
  5. Select the functions you want to compare from the table in the Functions window. + You must select one or more rows in the table.
  6. +
  7. Right mouse click and select the Compare Selected Functions option, + OR click the create-comparison icon on the tool bar.
-

This will display a -Function Comparison -window which allows the functions to be viewed -side by side.

+ +

The resulting Function Comparison +window will allow the functions to be viewed side by side.

+

 

diff --git a/Ghidra/Features/Base/src/main/help/help/topics/FunctionWindowPlugin/images/FunctionWindow.png b/Ghidra/Features/Base/src/main/help/help/topics/FunctionWindowPlugin/images/FunctionWindow.png index e15e3593c73fb8648c599d388ab942c10627067a..2891ce15329a59046de5a3945a169099bc964105 100644 GIT binary patch literal 25228 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 Qmx&W9N*ao#^7lgi2a#uEumAu6 literal 25104 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 M+>rfK+Q9pN07t~yTL1t6 diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/CompareFunctionsAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/CompareFunctionsAction.java deleted file mode 100644 index 2cd2242edb..0000000000 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/CompareFunctionsAction.java +++ /dev/null @@ -1,86 +0,0 @@ -/* ### - * IP: GHIDRA - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ghidra.app.plugin.core.functioncompare; - -import java.util.ArrayList; - -import docking.ActionContext; -import docking.action.MenuData; -import ghidra.app.context.*; -import ghidra.program.model.listing.*; -import ghidra.program.util.ProgramSelection; -import ghidra.util.HelpLocation; -import ghidra.util.Msg; - -/** - * An action that displays a function comparison panel for the two functions that are selected - * in the program. - */ -public class CompareFunctionsAction extends ProgramContextAction { - - FunctionComparisonPlugin functionComparisonPlugin; - - /** - * Constructs an action for displaying a panel that allows the user to compare functions. - * @param plugin the plugin that owns this action. - */ - CompareFunctionsAction(FunctionComparisonPlugin plugin) { - super("Compare Two Functions", plugin.getName()); - functionComparisonPlugin = plugin; - - // TODO no icon for now, while this action is at the top-level menu. When we put it in - // its final resting place, we can put the icon back. - // ImageIcon icon = ResourceManager.loadImage("images/page_white_c.png"); - setPopupMenuData(new MenuData(new String[] { "Compare Selected Functions..." }, null, - FunctionComparisonPlugin.FUNCTION_MENU_SUBGROUP, MenuData.NO_MNEMONIC, - "Z_End" /* See the FunctionPlugin for this value */)); - - setHelpLocation(new HelpLocation("FunctionComparison", "Compare_Selected_Functions")); - } - - @Override - public boolean isAddToPopup(ActionContext context) { - return (context instanceof ListingActionContext); - } - - @Override - protected boolean isEnabledForContext(ProgramActionContext context) { - return (context instanceof ListingActionContext); - } - - @Override - public void actionPerformed(ProgramActionContext context) { - - if (context instanceof ListingActionContext) { - ListingActionContext listingContext = (ListingActionContext) context; - ProgramSelection selection = listingContext.getSelection(); - Program program = listingContext.getProgram(); - FunctionManager functionManager = program.getFunctionManager(); - ArrayList functionList = new ArrayList<>(); - FunctionIterator functionIter = functionManager.getFunctions(selection, true); - for (Function selectedFunction : functionIter) { - functionList.add(selectedFunction); - } - Function[] functions = functionList.toArray(new Function[functionList.size()]); - if (functions.length < 2) { - String message = "You must select at least two functions in the current program."; - Msg.showError(this, null, "Compare Functions", message); - return; - } - functionComparisonPlugin.showFunctionComparisonProvider(functions); - } - } -} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionChoiceComparisonPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionChoiceComparisonPanel.java deleted file mode 100644 index e20d3f7a10..0000000000 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionChoiceComparisonPanel.java +++ /dev/null @@ -1,447 +0,0 @@ -/* ### - * IP: GHIDRA - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ghidra.app.plugin.core.functioncompare; - -import java.awt.*; -import java.awt.event.InputEvent; -import java.util.*; - -import javax.swing.*; - -import docking.ActionContext; -import docking.ComponentProvider; -import docking.action.*; -import docking.help.Help; -import docking.help.HelpService; -import docking.widgets.combobox.GComboBox; -import docking.widgets.fieldpanel.internal.FieldPanelCoordinator; -import ghidra.app.util.viewer.util.CodeComparisonPanel; -import ghidra.app.util.viewer.util.CodeComparisonPanelActionContext; -import ghidra.framework.plugintool.PluginTool; -import ghidra.program.model.listing.Function; -import ghidra.program.model.listing.Program; -import ghidra.util.HelpLocation; -import resources.MultiIcon; -import resources.ResourceManager; -import resources.icons.TranslateIcon; - -/** - * Creates a panel for comparing two or more functions. - * If there are multiple functions to display within either the left or right side of this panel, - * then a combo box will appear above the left and right side of the {@link CodeComparisonPanel}s. - * Each combo box will allow the user to choose which function to display on that side of the panel. - */ -public abstract class FunctionChoiceComparisonPanel extends FunctionComparisonPanel { - - private JPanel choicePanel; // null if only 1 left function and 1 right function. - protected WrappedFunction[] leftWrappedFunctions = new WrappedFunction[] {}; - protected WrappedFunction[] rightWrappedFunctions = new WrappedFunction[] {}; - protected int leftIndex = 0; - protected int rightIndex = 0; - protected JComboBox leftComboBox; - protected JComboBox rightComboBox; - private NextFunctionAction nextFunctionAction; - private PreviousFunctionAction previousFunctionAction; - private static final String FUNCTION_NAVIGATE_GROUP = "A9_FunctionNavigate"; - private static final Icon FUNCTION_ICON = - new TranslateIcon(ResourceManager.loadImage("images/FunctionScope.gif"), -5, -2); - private static final Icon NEXT_ICON = - new TranslateIcon(ResourceManager.loadImage("images/arrow_down.png"), 3, 1); - private static final Icon PREVIOUS_ICON = - new TranslateIcon(ResourceManager.loadImage("images/arrow_up.png"), 3, 1); - private static final Icon NEXT_FUNCTION_ICON = new MultiIcon(NEXT_ICON, FUNCTION_ICON); - private static final Icon PREVIOUS_FUNCTION_ICON = new MultiIcon(PREVIOUS_ICON, FUNCTION_ICON); - protected static final HelpService help = Help.getHelpService(); - protected static final String HELP_TOPIC = "FunctionComparison"; - private MyFunctionComparator myFunctionComparator = new MyFunctionComparator(); - - /** - * Creates a panel for comparing two or more functions. - * - * @param provider the GUI provider that includes this panel. - * @param tool the tool containing this panel - * @param leftFunction the function displayed in the left side of the panel. - * @param rightFunction the function displayed in the right side of the panel. - */ - protected FunctionChoiceComparisonPanel(ComponentProvider provider, PluginTool tool, - Function leftFunction, Function rightFunction) { - super(provider, tool, leftFunction, rightFunction); - } - - /** - * Gets the functions for the left side of the panel. - *
- * These functions are sorted ascending on program and function name. - * The primary sort is on program including pathname. - * The secondary sort is on function including namespace. - * - * @return the functions that can be displayed on the left side. - */ - public Function[] getLeftFunctions() { - Function[] leftFunctions = new Function[leftWrappedFunctions.length]; - for (int i = 0; i < leftWrappedFunctions.length; i++) { - leftFunctions[i] = leftWrappedFunctions[i].getFunction(); - } - return leftFunctions; - } - - /** - * Gets the functions for the right side of the panel. - *
- * These functions are sorted ascending on program and function name. - * The primary sort is on program including pathname. - * The secondary sort is on function including namespace. - * - * @return the functions that can be displayed on the right side. - */ - public Function[] getRightFunctions() { - Function[] rightFunctions = new Function[rightWrappedFunctions.length]; - for (int i = 0; i < rightWrappedFunctions.length; i++) { - rightFunctions[i] = rightWrappedFunctions[i].getFunction(); - } - return rightFunctions; - } - - /** - * Gets an array of WrappedFunctions for the array of functions passed as a parameter. - * @param functionArray the functions to convert to WrappedFunctions. - * @return the WrappedFunctions. - */ - protected WrappedFunction[] getWrappedFunctions(Function[] functionArray) { - WrappedFunction[] wrappedFunctionArray = new WrappedFunction[functionArray.length]; - for (int i = 0; i < functionArray.length; i++) { - wrappedFunctionArray[i] = new WrappedFunction(functionArray[i]); - } - return wrappedFunctionArray; - } - - /** - * Adds a panel with combo boxes for choosing the currently displayed function in the - * left and right panel. This also populates the combo boxes with the functions. - */ - protected void addChoicePanel() { - choicePanel = new JPanel(new GridLayout(1, 2)); - choicePanel.add(createLeftChoicePanel()); - choicePanel.add(createRightChoicePanel()); - add(choicePanel, BorderLayout.NORTH); - - help.registerHelp(choicePanel, new HelpLocation(HELP_TOPIC, "Compare Multiple Functions")); - } - - /** - * Updates the selected left function in the combo box based on the current left index. - */ - protected void adjustSelectedLeftFunction() { - WrappedFunction leftWrappedFunction = - (leftIndex < leftWrappedFunctions.length) ? leftWrappedFunctions[leftIndex] : null; - leftComboBox.setSelectedItem(leftWrappedFunction); - } - - /** - * Updates the selected right function in the combo box based on the current right index. - */ - protected void adjustSelectedRightFunction() { - WrappedFunction rightWrappedFunction = - (rightIndex < rightWrappedFunctions.length) ? rightWrappedFunctions[rightIndex] : null; - rightComboBox.setSelectedItem(rightWrappedFunction); - } - - private Component createLeftChoicePanel() { - JPanel panel = new JPanel(new BorderLayout()); - leftComboBox = new GComboBox<>(leftWrappedFunctions); - adjustSelectedLeftFunction(); - leftComboBox.addItemListener(e -> { - WrappedFunction wrappedFunction = (WrappedFunction) leftComboBox.getSelectedItem(); - Function function = (wrappedFunction != null) ? wrappedFunction.getFunction() : null; - setLeftFunction(function); - }); - panel.add(leftComboBox, BorderLayout.CENTER); - return panel; - } - - private Component createRightChoicePanel() { - JPanel panel = new JPanel(new BorderLayout()); - rightComboBox = new GComboBox<>(rightWrappedFunctions); - adjustSelectedRightFunction(); - rightComboBox.addItemListener(e -> { - WrappedFunction wrappedFunction = (WrappedFunction) rightComboBox.getSelectedItem(); - Function function = (wrappedFunction != null) ? wrappedFunction.getFunction() : null; - setRightFunction(function); - }); - panel.add(rightComboBox, BorderLayout.CENTER); - return panel; - } - - /** - * Creates actions for displaying the next or previous function if we are using combo boxes. - */ - protected void createActions() { - if (choicePanel != null) { - nextFunctionAction = new NextFunctionAction(); - previousFunctionAction = new PreviousFunctionAction(); - } - } - - @Override - public DockingAction[] getCodeComparisonActions() { - DockingAction[] otherActions = super.getCodeComparisonActions(); - if (choicePanel == null) { - return otherActions; - } - DockingAction[] myActions = - new DockingAction[] { nextFunctionAction, previousFunctionAction }; - DockingAction[] actions = new DockingAction[otherActions.length + myActions.length]; - System.arraycopy(otherActions, 0, actions, 0, otherActions.length); - System.arraycopy(myActions, 0, actions, otherActions.length, myActions.length); - return actions; - } - - private boolean isValidPanelContext(ActionContext context) { - if (context instanceof CodeComparisonPanelActionContext) { - return choicePanel != null; - } - return false; - } - - private void nextFunction() { - CodeComparisonPanel currentComponent = - getCurrentComponent(); - if (currentComponent == null) { - return; - } - boolean leftHasFocus = currentComponent.leftPanelHasFocus(); - if (leftHasFocus) { - if (leftIndex < (leftWrappedFunctions.length - 1)) { - leftComboBox.setSelectedIndex(++leftIndex); - } - else { - outputNoNextPreviousMessage(true, leftHasFocus); - return; - } - } - else { // right has focus. - if (rightIndex < (rightWrappedFunctions.length - 1)) { - rightComboBox.setSelectedIndex(++rightIndex); - } - else { - outputNoNextPreviousMessage(true, leftHasFocus); - return; - } - } - } - - private void previousFunction() { - CodeComparisonPanel currentComponent = - getCurrentComponent(); - if (currentComponent == null) { - return; - } - boolean leftHasFocus = currentComponent.leftPanelHasFocus(); - if (leftHasFocus) { - if (leftIndex > 0) { - leftComboBox.setSelectedIndex(--leftIndex); - } - else { - outputNoNextPreviousMessage(false, leftHasFocus); - return; - } - } - else { // right has focus. - if (rightIndex > 0) { - rightComboBox.setSelectedIndex(--rightIndex); - } - else { - outputNoNextPreviousMessage(false, leftHasFocus); - return; - } - } - } - - private void outputNoNextPreviousMessage(boolean forward, boolean isFirstListing) { - tool.setStatusInfo("There isn't another " + (forward ? "next " : "previous ") + - "function for the " + (isFirstListing ? "first" : "second") + " listing."); - } - - /** - * Action to display the next function in the currently focused side of the function - * comparison panel if possible. - */ - protected class NextFunctionAction extends DockingAction { - - NextFunctionAction() { - super("Compare Next Function", provider.getOwner()); - setKeyBindingData( - new KeyBindingData('N', InputEvent.CTRL_MASK | InputEvent.SHIFT_MASK)); - setDescription("Compare the next function for the side with focus."); - setPopupMenuData(new MenuData(new String[] { "Compare The Next Function" }, - NEXT_FUNCTION_ICON, FUNCTION_NAVIGATE_GROUP)); - ToolBarData newToolBarData = - new ToolBarData(NEXT_FUNCTION_ICON, FUNCTION_NAVIGATE_GROUP); - setToolBarData(newToolBarData); - - HelpLocation helpLocation = new HelpLocation(HELP_TOPIC, "Compare The Next Function"); - setHelpLocation(helpLocation); - setEnabled(true); - } - - @Override - public boolean isValidContext(ActionContext context) { - return isValidPanelContext(context); - } - - @Override - public void actionPerformed(ActionContext context) { - if (isValidContext(context)) { - nextFunction(); - } - } - } - - /** - * Action to display the previous function in the currently focused side of the function - * comparison panel if possible. - */ - protected class PreviousFunctionAction extends DockingAction { - - PreviousFunctionAction() { - super("Compare Previous Function", provider.getOwner()); - setKeyBindingData( - new KeyBindingData('P', InputEvent.CTRL_MASK | InputEvent.SHIFT_MASK)); - setDescription("Compare the previous function for the side with focus."); - setPopupMenuData(new MenuData(new String[] { "Compare The Previous Function" }, - PREVIOUS_FUNCTION_ICON, FUNCTION_NAVIGATE_GROUP)); - ToolBarData newToolBarData = - new ToolBarData(PREVIOUS_FUNCTION_ICON, FUNCTION_NAVIGATE_GROUP); - setToolBarData(newToolBarData); - - HelpLocation helpLocation = - new HelpLocation(HELP_TOPIC, "Compare The Previous Function"); - setHelpLocation(helpLocation); - setEnabled(true); - } - - @Override - public boolean isValidContext(ActionContext context) { - return isValidPanelContext(context); - } - - @Override - public void actionPerformed(ActionContext context) { - if (isValidContext(context)) { - previousFunction(); - } - } - } - - /** - * Gets a sorted array of the functions for the indicated set of functions. - * This sorts the functions first by program and then by function name. - * The primary sort is ascending on the program's pathname. - * The secondary sort is ascending on the function's name which includes its namespace. - * - * @param functions the set of functions - * @return the sorted array of functions - */ - protected Function[] getSortedFunctions(Set functions) { - Function[] sortedFunctions = functions.toArray(new Function[functions.size()]); - Arrays.sort(sortedFunctions, myFunctionComparator); - return sortedFunctions; - } - - /** - * Gets a sorted array of the functions for the indicated array of functions. - * This sorts the functions first by program and then by function name. - * The primary sort is ascending on the program's pathname. - * The secondary sort is ascending on the function's name which includes its namespace. - *
- * The original function array is not modified. - * - * @param functions the array of functions - * @return a new sorted array of functions - */ - protected Function[] getSortedFunctions(Function[] functions) { - Function[] sortedFunctions = Arrays.copyOf(functions, functions.length); - Arrays.sort(sortedFunctions, myFunctionComparator); - return sortedFunctions; - } - - /** - * A comparator for functions that sorts the functions first by program and then by - * function name. - * The primary sort is ascending on the program's pathname. - * The secondary sort is ascending on the function's name which includes its namespace. - */ - private class MyFunctionComparator implements Comparator { - - @Override - public int compare(Function function1, Function function2) { - if (function1 == null) { - if (function2 == null) { - return 0; - } - return -1; - } - if (function1 == function2) { - return 0; - } - String function1Name = function1.getName(true); - String function2Name = function2.getName(true); - - Program program1 = function1.getProgram(); - Program program2 = function2.getProgram(); - - String program1Name = program1.getDomainFile().getPathname(); - String program2Name = program2.getDomainFile().getPathname(); - - int comparePrograms = program1Name.compareTo(program2Name); - if (comparePrograms != 0) { - return comparePrograms; - } - - return function1Name.compareTo(function2Name); - } - } - - /** - * Class that allows us to display a more informative string in the combo box for each function. - */ - protected static class WrappedFunction { - - private Function function; - - private WrappedFunction(Function function) { - this.function = function; - } - - protected Function getFunction() { - return function; - } - - @Override - public String toString() { - return getFunctionTitle(); - } - - private String getFunctionTitle() { - if (function == null) { - return "none"; - } - Program program = function.getProgram(); - return function.getName(true) + "()" + - ((program != null) ? (" in " + program.getDomainFile().getPathname() + "") : ""); - } - } -} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparison.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparison.java new file mode 100644 index 0000000000..f093214663 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparison.java @@ -0,0 +1,131 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.functioncompare; + +import java.util.*; + +import ghidra.app.services.FunctionComparisonModel; +import ghidra.program.model.listing.Function; + +/** + * Defines the structure of a function comparison. The relationship is strictly + * one-to-many; a single source function may be associated with one + * or more target functions. + *

+ * This is the basic unit for the + * {@link FunctionComparisonModel function comparison data model} + */ +public class FunctionComparison implements Comparable { + + private Function source; + + /** Use a tree so functions are always kept in sorted order */ + private Set targets = new TreeSet<>(new FunctionComparator()); + + /** + * Returns the source function + * + * @return the source function + */ + public Function getSource() { + return source; + } + + /** + * Returns the set of targets, in sorted order by function name + * + * @return the set of targets + */ + public Set getTargets() { + return targets; + } + + /** + * Sets a given function as the comparison source + * + * @param function the source function + */ + public void setSource(Function function) { + source = function; + } + + /** + * Adds a target function to the comparison + * + * @param function the function to add to the target list + */ + public void addTarget(Function function) { + targets.add(function); + } + + /** + * Adds a set of functions to the target list + * + * @param functions the functions to add + */ + public void addTargets(Set functions) { + targets.addAll(functions); + } + + /** + * Removes the given function from the target list. + *

+ * Note that the target list is a {@link Set}, so there will only ever + * be at most one entry that matches the given function + * + * @param function the function to remove + */ + public void removeTarget(Function function) { + targets.remove(function); + } + + /** + * Removes all targets from the comparison + */ + public void clearTargets() { + targets.clear(); + } + + /** + * Ensures that FunctionComparison objects are always ordered according + * to the source function name + */ + @Override + public int compareTo(FunctionComparison o) { + if (o == null) { + return 1; + } + return getSource().getName().compareTo(o.getSource().getName()); + } + + /** + * Forces an ordering on {@link Function} objects by name. This is + * to ensure that the list of targets is kept in sorted order at all times. + */ + class FunctionComparator implements Comparator { + + @Override + public int compare(Function o1, Function o2) { + if (o1 == o2) { + return 0; + } + if (o2 == null) { + return 1; + } + return o1.getName().compareTo(o2.getName()); + } + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonData.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonData.java new file mode 100644 index 0000000000..995f863a14 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonData.java @@ -0,0 +1,154 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.functioncompare; + +import ghidra.program.model.address.AddressSet; +import ghidra.program.model.address.AddressSetView; +import ghidra.program.model.listing.*; + +/** + * Defines the information being displayed in the left or right panels + * of a {@link FunctionComparisonPanel}, which can display either + * {@link Function functions}, {@link Data data}, or specified + * {@link AddressSet address sets}. At any given time, only one of the + * Function or Data attributes may be set; the other will be + * set to null. + */ +class FunctionComparisonData { + + protected Program program; + protected Function function; + protected Data data; + protected AddressSetView addressSet = new AddressSet(); + + /** + * Returns the program for this model + * + * @return the program, or null if not set + */ + public Program getProgram() { + return program; + } + + /** + * Sets the program for this model + * + * @param program the program to set + */ + public void setProgram(Program program) { + this.program = program; + } + + /** + * Returns the function for this model + * + * @return the function, or null if not set + */ + public Function getFunction() { + return function; + } + + /** + * Sets the function for this model + * + * @param function the function to set + */ + public void setFunction(Function function) { + if (function == null) { + clear(); + return; + } + this.function = function; + this.data = null; + this.program = function.getProgram(); + this.addressSet = function.getBody(); + } + + /** + * Returns the data for this model + * + * @return the data, or null if not set + */ + public Data getData() { + return data; + } + + /** + * Sets the data for this model + * + * @param data the data to set + */ + public void setData(Data data) { + if (data == null) { + clear(); + return; + } + this.data = data; + this.function = null; + this.program = data.getProgram(); + this.addressSet = new AddressSet(data.getMinAddress(), data.getMaxAddress()); + } + + /** + * Returns the address set for this model + * + * @return the address set, or null if not set + */ + public AddressSetView getAddressSet() { + return addressSet; + } + + /** + * Sets the address for this model + * + * @param addressSet the addressSet to set + */ + public void setAddressSet(AddressSetView addressSet) { + this.addressSet = addressSet; + this.data = null; + this.function = null; + } + + /** + * Returns true if the data being managed by this model is of type + * {@link Data} + * + * @return true if this model is set to display {@link Data} + */ + public boolean isData() { + return data != null; + } + + /** + * Returns true if the data being managed by this model is of type + * {@link Function} + * + * @return true if this model is set to display a {@link Function} + */ + public boolean isFunction() { + return function != null; + } + + /** + * Resets all fields in this model to a nominal state + */ + public void clear() { + this.function = null; + this.data = null; + this.addressSet = new AddressSet(); + this.program = null; + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonModelListener.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonModelListener.java new file mode 100644 index 0000000000..40bdea601b --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonModelListener.java @@ -0,0 +1,34 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.functioncompare; + +import java.util.List; + +import ghidra.app.services.FunctionComparisonModel; + +/** + * Allows subscribers to register for {@link FunctionComparisonModel function + * comparison model} changes + */ +public interface FunctionComparisonModelListener { + + /** + * Invoked when the comparison model has changed + * + * @param model the current state of the model + */ + public void modelChanged(List model); +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonPanel.java index bafd789b99..77f854d701 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonPanel.java @@ -36,6 +36,7 @@ import docking.widgets.tabbedpane.DockingTabRenderer; import ghidra.app.util.viewer.listingpanel.ListingCodeComparisonPanel; import ghidra.app.util.viewer.util.CodeComparisonPanel; import ghidra.framework.options.SaveState; +import ghidra.framework.plugintool.ComponentProviderAdapter; import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.address.AddressSet; import ghidra.program.model.address.AddressSetView; @@ -46,15 +47,17 @@ import ghidra.util.classfinder.ClassSearcher; import resources.ResourceManager; /** - * A panel for displaying two functions side by side for comparison purposes.
- * This panel is intended to discover and provide multiple different types of - * CodeComparisonPanels. Each type of {@link CodeComparisonPanel} will be in its own tab. - * The user can only view one type of CodeComparisonPanel at a time, but can select the - * currently displayed one. - * + * A panel for displaying {@link Function functions}, {@link Data data}, or + * {@link AddressSet address sets} side-by-side for comparison purposes + *

+ * Note: This is strictly for a one-to-one comparison; if multiple items are to + * be compared, use a {@link MultiFunctionComparisonPanel} */ public class FunctionComparisonPanel extends JPanel implements ChangeListener { + private FunctionComparisonData leftComparisonData; + private FunctionComparisonData rightComparisonData; + private static final String DEFAULT_CODE_COMPARISON_VIEW = ListingCodeComparisonPanel.TITLE; private static final String COMPARISON_VIEW_DISPLAYED = "COMPARISON_VIEW_DISPLAYED"; private static final String CODE_COMPARISON_LOCK_SCROLLING_TOGETHER = @@ -71,55 +74,625 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener { private static final String DUAL_SCROLLING_HELP_TOPIC = "FunctionComparison"; private JTabbedPane tabbedPane; - private HashMap tabNameToComponentMap; + private Map tabNameToComponentMap; protected PluginTool tool; - protected ComponentProvider provider; - protected Program leftProgram; - protected Program rightProgram; - private Function leftFunction; - private Function rightFunction; - private Data leftData; - private Data rightData; - private AddressSetView leftAddressSet = new AddressSet(); - private AddressSetView rightAddressSet = new AddressSet(); + protected ComponentProviderAdapter provider; private List> codeComparisonPanels; private ToggleScrollLockAction toggleScrollLockAction; private boolean syncScrolling = false; /** - * Creates a panel for comparing two functions. - * @param provider the GUI provider that includes this panel. + * Constructor + * + * @param provider the GUI provider that includes this panel * @param tool the tool containing this panel - * @param leftFunction the function displayed in the left side of the panel. - * @param rightFunction the function displayed in the right side of the panel. + * @param leftFunction the function displayed in the left side of the panel + * @param rightFunction the function displayed in the right side of the panel */ - public FunctionComparisonPanel(ComponentProvider provider, PluginTool tool, + public FunctionComparisonPanel(ComponentProviderAdapter provider, PluginTool tool, Function leftFunction, Function rightFunction) { this.provider = provider; this.tool = tool; - this.leftFunction = leftFunction; - this.rightFunction = rightFunction; - leftProgram = (leftFunction != null) ? leftFunction.getProgram() : null; - rightProgram = (rightFunction != null) ? rightFunction.getProgram() : null; + this.leftComparisonData = new FunctionComparisonData(); + this.rightComparisonData = new FunctionComparisonData(); + this.leftComparisonData.setFunction(leftFunction); + this.rightComparisonData.setFunction(rightFunction); this.codeComparisonPanels = getCodeComparisonPanels(); - this.leftAddressSet = (leftFunction != null) ? leftFunction.getBody() : new AddressSet(); - this.rightAddressSet = (rightFunction != null) ? rightFunction.getBody() : new AddressSet(); tabNameToComponentMap = new HashMap<>(); - create(); + createMainPanel(); createActions(); setScrollingSyncState(true); help.registerHelp(this, new HelpLocation(HELP_TOPIC, "Function Comparison")); } - // Discovers the CodeComparisonPanels which are extension points. + /** + * Load the given functions into the views of this panel + * + * @param leftFunction The function for the left side of the panel + * @param rightFunction The function for the right side of the panel + */ + public void loadFunctions(Function leftFunction, Function rightFunction) { + leftComparisonData.setFunction(leftFunction); + rightComparisonData.setFunction(rightFunction); + + CodeComparisonPanel activePanel = + getActiveComparisonPanel(); + if (activePanel != null) { + activePanel.loadFunctions(leftComparisonData.getFunction(), + rightComparisonData.getFunction()); + } + } + + /** + * Load the given data into the views of this panel + * + * @param leftData The data for the left side of the panel + * @param rightData The data for the right side of the panel + */ + public void loadData(Data leftData, Data rightData) { + leftComparisonData.setData(leftData); + rightComparisonData.setData(rightData); + + CodeComparisonPanel activePanel = + getActiveComparisonPanel(); + if (activePanel != null) { + activePanel.loadData(leftComparisonData.getData(), + rightComparisonData.getData()); + } + } + + /** + * Load the given addresses of the indicated programs into the views of + * this panel + * + * @param leftProgram the program for the left side of the panel + * @param rightProgram the program for the right side of the panel + * @param leftAddresses addresses for the info to display in the left side + * of the panel + * @param rightAddresses addresses for the info to display in the right + * side of the panel + */ + public void loadAddresses(Program leftProgram, Program rightProgram, + AddressSetView leftAddresses, AddressSetView rightAddresses) { + leftComparisonData.setAddressSet(leftAddresses); + rightComparisonData.setAddressSet(rightAddresses); + leftComparisonData.setProgram(leftProgram); + rightComparisonData.setProgram(rightProgram); + CodeComparisonPanel activePanel = + getActiveComparisonPanel(); + if (activePanel != null) { + activePanel.loadAddresses(leftComparisonData.getProgram(), + rightComparisonData.getProgram(), leftComparisonData.getAddressSet(), + rightComparisonData.getAddressSet()); + } + } + + /** + * Removes the functions in the left/right panels if they are in the + * given set + * + * @param functions the functions to remove + */ + public void removeFunctions(Set functions) { + if (functions.contains(leftComparisonData.getFunction())) { + setLeftFunction(null); + } + if (functions.contains(rightComparisonData.getFunction())) { + setRightFunction(null); + } + } + + /** + * Get the actions for this FunctionComparisonPanel + * + * @return an array containing the actions + */ + public DockingAction[] getActions() { + DockingAction[] actions = new DockingAction[] { toggleScrollLockAction }; + return actions; + } + + /** + * Gets a description to help distinguish this comparison panel from others + * + * @return the description + */ + public String getDescription() { + Function leftFunc = leftComparisonData.getFunction(); + Function rightFunc = rightComparisonData.getFunction(); + Data leftData = leftComparisonData.getData(); + Data rightData = rightComparisonData.getData(); + + if (leftFunc != null && rightFunc != null) { + return "Function comparison of " + leftFunc.getName(true) + " & " + + rightFunc.getName(true); + } + if (leftData != null && rightData != null) { + return "Function comparison of " + leftData.getDataType().getName() + " & " + + rightData.getDataType().getName(); + } + + // Otherwise give a simple description for address sets + return "Function comparison"; + } + + /** + * Clear both sides of this panel + */ + public void clear() { + leftComparisonData.clear(); + rightComparisonData.clear(); + + // Setting the addresses to be displayed to null effectively clears + // the display + CodeComparisonPanel activePanel = + getActiveComparisonPanel(); + if (activePanel != null) { + activePanel.loadAddresses(null, null, null, null); + } + } + + /** + * Returns true if the comparison window has no functions to display in + * either the left or right panel + * + * @return true if the comparison window has no functions to display + */ + public boolean isEmpty() { + return getLeftFunction() == null || getRightFunction() == null; + } + + /** + * Gets the ListingCodeComparisonPanel being displayed by this panel + * if one exists + * + * @return the ListingCodeComparisonPanel or null + */ + public ListingCodeComparisonPanel getDualListingPanel() { + for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { + JComponent component = codeComparisonPanel.getComponent(); + if (component instanceof ListingCodeComparisonPanel) { + return (ListingCodeComparisonPanel) component; + } + } + return null; + } + + /** + * Invoked when a new tab on the panel has been selected + */ + @Override + public void stateChanged(ChangeEvent e) { + tabChanged(); + } + + /** + * Refreshes the contents of the panel + */ + public void reload() { + // do nothing by default; override in subs if necessary + } + + /** + * Set the current tabbed panel to be the component with the given name + * + * @param name name of view to set as the current tab + * @return true if the named view was found in the provider map + */ + public boolean setCurrentTabbedComponent(String name) { + + JComponent component = tabNameToComponentMap.get(name); + if (component != null) { + if (tabbedPane.getSelectedComponent() == component) { + tabChanged(); + } + tabbedPane.setSelectedComponent(component); + } + return component != null; + } + + /** + * Get the name of the current comparison panel being viewed + * + * @return the tab name, or null if there is nothing selected + */ + public String getCurrentComponentName() { + int selectedIndex = tabbedPane.getSelectedIndex(); + if (selectedIndex >= 0) { + return tabbedPane.getTitleAt(selectedIndex); + } + return null; + } + + /** + * Get the number of views in the tabbed pane + */ + int getNumberOfTabbedComponents() { + return tabNameToComponentMap.size(); + } + + /** + * Remove all views in the tabbed pane + */ + public void dispose() { + tool.removeComponentProvider(provider); + tabbedPane.removeAll(); + + setVisible(false); + for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { + codeComparisonPanel.dispose(); + } + } + + /** + * Create the main tabbed panel + */ + private void createMainPanel() { + tabbedPane = new JTabbedPane(); + + tabbedPane.addChangeListener(this); + setLayout(new BorderLayout()); + + add(tabbedPane, BorderLayout.CENTER); + setPreferredSize(new Dimension(200, 300)); + + for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { + codeComparisonPanel.loadFunctions(leftComparisonData.getFunction(), + rightComparisonData.getFunction()); + JComponent component = codeComparisonPanel.getComponent(); + tabbedPane.add(codeComparisonPanel.getTitle(), component); + tabNameToComponentMap.put(codeComparisonPanel.getTitle(), component); + } + } + + /** + * Invoked when there is a tab change. This loads the active tab with + * the appropriate data to be compared. + */ + private void tabChanged() { + CodeComparisonPanel activePanel = + getActiveComparisonPanel(); + if (activePanel == null) { + return; // initializing + } + + if (leftComparisonData.isFunction() || rightComparisonData.isFunction()) { + activePanel.loadFunctions(leftComparisonData.getFunction(), + rightComparisonData.getFunction()); + } + else if (leftComparisonData.isData() || rightComparisonData.isData()) { + activePanel.loadData(leftComparisonData.getData(), rightComparisonData.getData()); + } + else { + activePanel.loadAddresses(leftComparisonData.getProgram(), + rightComparisonData.getProgram(), leftComparisonData.getAddressSet(), + rightComparisonData.getAddressSet()); + } + } + + /** + * Returns the comparison panel that is in the selected tab + * + * @return the currently selected comparison panel, or null if nothing + * selected + */ + private CodeComparisonPanel getActiveComparisonPanel() { + JComponent c = (JComponent) tabbedPane.getSelectedComponent(); + for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { + JComponent component = codeComparisonPanel.getComponent(); + if (c == component) { + return codeComparisonPanel; + } + } + + return null; + } + + /** + * Gets the function currently displayed in the left side of this panel + * + * @return the left function or null + */ + public Function getLeftFunction() { + return leftComparisonData.getFunction(); + } + + /** + * Sets the function to display in the left side of this panel + * + * @param function the function to display + */ + protected void setLeftFunction(Function function) { + loadFunctions(function, rightComparisonData.getFunction()); + } + + /** + * Gets the function currently displayed in the right side of this panel + * + * @return the right function or null + */ + public Function getRightFunction() { + return rightComparisonData.getFunction(); + } + + /** + * Sets the function to display in the right side of this panel + * + * @param function the function to display + */ + protected void setRightFunction(Function function) { + loadFunctions(leftComparisonData.getFunction(), function); + } + + /** + * Gets the data displayed in the left side of this panel + * + * @return the left data or null + */ + public Data getLeftData() { + return leftComparisonData.getData(); + } + + /** + * Gets the data displayed in the right side of this panel + * + * @return the right data + */ + public Data getRightData() { + return rightComparisonData.getData(); + } + + /** + * Enables/disables mouse navigation for all the CodeComparisonPanels + * displayed by this panel + * + * @param enabled true to enable mouse navigation in the panels + */ + public void setMouseNavigationEnabled(boolean enabled) { + for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { + codeComparisonPanel.setMouseNavigationEnabled(enabled); + } + } + + /** + * Sets up the FunctionComparisonPanel and which CodeComparisonPanel is currently + * displayed based on the specified saveState + * + * @param prefix identifier to prepend to any save state names to make them unique + * @param saveState the save state for retrieving information + */ + public void readConfigState(String prefix, SaveState saveState) { + String currentTabView = + saveState.getString(prefix + COMPARISON_VIEW_DISPLAYED, DEFAULT_CODE_COMPARISON_VIEW); + setCurrentTabbedComponent(currentTabView); + setScrollingSyncState( + saveState.getBoolean(prefix + CODE_COMPARISON_LOCK_SCROLLING_TOGETHER, true)); + ListingCodeComparisonPanel dualListingPanel = getDualListingPanel(); + if (dualListingPanel != null) { + dualListingPanel.readConfigState(prefix, saveState); + } + } + + /** + * Saves the information to the save state about the FunctionComparisonPanel and + * which CodeComparisonPanel is currently displayed + * + * @param prefix identifier to prepend to any save state names to make them unique + * @param saveState the save state where the information gets written + */ + public void writeConfigState(String prefix, SaveState saveState) { + String currentComponentName = getCurrentComponentName(); + if (currentComponentName != null) { + saveState.putString(prefix + COMPARISON_VIEW_DISPLAYED, getCurrentComponentName()); + } + saveState.putBoolean(prefix + CODE_COMPARISON_LOCK_SCROLLING_TOGETHER, isScrollingSynced()); + ListingCodeComparisonPanel dualListingPanel = getDualListingPanel(); + if (dualListingPanel != null) { + dualListingPanel.writeConfigState(prefix, saveState); + } + } + + /** + * Gets all actions for the FunctionComparisonPanel and all CodeComparisonPanels in this + * FunctionComparisonPanel + * + * @return the code comparison actions + */ + public DockingAction[] getCodeComparisonActions() { + ArrayList dockingActionList = new ArrayList<>(); + // Get actions for this functionComparisonPanel + DockingAction[] functionComparisonActions = getActions(); + for (DockingAction dockingAction : functionComparisonActions) { + dockingActionList.add(dockingAction); + } + // Get actions for each CodeComparisonPanel + for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { + DockingAction[] actions = codeComparisonPanel.getActions(); + for (DockingAction dockingAction : actions) { + dockingActionList.add(dockingAction); + } + } + return dockingActionList.toArray(new DockingAction[dockingActionList.size()]); + } + + /** + * Sets the prefixes that are to be prepended to the title displayed for each side of + * each CodeComparisonPanel + * + * @param leftTitlePrefix the prefix to prepend to the left titles + * @param rightTitlePrefix the prefix to prepend to the right titles + */ + public void setTitlePrefixes(String leftTitlePrefix, String rightTitlePrefix) { + Component[] components = tabbedPane.getComponents(); + for (Component component : components) { + if (component instanceof CodeComparisonPanel) { + ((CodeComparisonPanel) component).setTitlePrefixes(leftTitlePrefix, + rightTitlePrefix); + } + } + } + + /** + * Returns the action context for a given mouse event and provider + * + * @param event the mouse event + * @param componentProvider the component provider + * @return the action context + */ + public ActionContext getActionContext(MouseEvent event, ComponentProvider componentProvider) { + Object source = (event != null) ? event.getSource() : null; + Component sourceComponent = (source instanceof Component) ? (Component) source : null; + ListingCodeComparisonPanel dualListingPanel = getDualListingPanel(); + // Is the action being taken on the dual listing. + if (dualListingPanel != null && dualListingPanel.isAncestorOf(sourceComponent)) { + return dualListingPanel.getActionContext(event, componentProvider); + } + return null; + } + + /** + * Determines if the layouts of the views are synchronized with respect + * to scrolling and location + * + * @return true if scrolling is synchronized between the two views + */ + public final boolean isScrollingSynced() { + return syncScrolling; + } + + /** + * Sets whether or not scrolling is synchronized + * + * @param syncScrolling true means synchronize scrolling and location + * between the two views + */ + public void setScrollingSyncState(boolean syncScrolling) { + if (isScrollingSynced() == syncScrolling) { + return; + } + toggleScrollLockAction.setSelected(syncScrolling); + toggleScrollLockAction.setToolBarData(new ToolBarData( + syncScrolling ? SYNC_SCROLLING_ICON : UNSYNC_SCROLLING_ICON, SCROLLING_GROUP)); + // Notify each comparison panel of the scrolling sync state. + for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { + codeComparisonPanel.setScrollingSyncState(syncScrolling); + } + this.syncScrolling = syncScrolling; + } + + /** + * Gets the currently displayed CodeComparisonPanel + * + * @return the current panel or null. + */ + public CodeComparisonPanel getDisplayedPanel() { + int selectedIndex = tabbedPane.getSelectedIndex(); + Component component = tabbedPane.getComponentAt(selectedIndex); + return (CodeComparisonPanel) component; + } + + /** + * Updates the enablement for all actions provided by each panel + */ + public void updateActionEnablement() { + for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { + codeComparisonPanel.updateActionEnablement(); + } + } + + /** + * Get the current code comparison panel being viewed + * + * @return null if there is no code comparison panel + */ + CodeComparisonPanel getCurrentComponent() { + return (CodeComparisonPanel) tabbedPane.getSelectedComponent(); + } + + /** + * Returns true if the clicked object is a tab + * + * @param event the mouse event + * @return true if the clicked object is a tab + */ + boolean isTabClick(MouseEvent event) { + Component component = event.getComponent(); + int tabCount = tabbedPane.getTabCount(); + for (int i = 0; i < tabCount; i++) { + DockingTabRenderer renderer = (DockingTabRenderer) tabbedPane.getTabComponentAt(i); + if (SwingUtilities.isDescendingFrom(component, renderer)) { + return true; + } + } + return false; + } + + /** + * Creates the actions available for this panel + */ + private void createActions() { + toggleScrollLockAction = new ToggleScrollLockAction(); + } + + /** + * Comparator that lets CodeComparisonPanels be sorted based on their + * title names + */ + private class CodeComparisonPanelComparator + implements Comparator> { + + @Override + public int compare(CodeComparisonPanel o1, + CodeComparisonPanel o2) { + if (o1 == o2) { + return 0; + } + String title1 = o1.getTitle(); + String title2 = o2.getTitle(); + return title1.compareTo(title2); + } + } + + /** + * Action that sets the scrolling state of the comparison panels + */ + private class ToggleScrollLockAction extends ToggleDockingAction { + ToggleScrollLockAction() { + super("Synchronize Scrolling of Dual View", provider.getName()); + setDescription("Lock/Unlock Synchronized Scrolling of Dual View"); + setToolBarData(new ToolBarData(UNSYNC_SCROLLING_ICON, SCROLLING_GROUP)); + setEnabled(true); + MenuData menuData = + new MenuData(new String[] { "Synchronize Scrolling" }, DUAL_SCROLLING_ACTION_GROUP); + setMenuBarData(menuData); + + setHelpLocation( + new HelpLocation(DUAL_SCROLLING_HELP_TOPIC, "Synchronize Scrolling of Dual View")); + } + + @Override + public void actionPerformed(ActionContext context) { + setScrollingSyncState(isSelected()); + } + } + + public List> getComparisonPanels() { + return codeComparisonPanels; + } + + /** + * Discovers the CodeComparisonPanels which are extension points + * + * @return the CodeComparisonPanels which are extension points + */ private List> getCodeComparisonPanels() { if (codeComparisonPanels == null) { codeComparisonPanels = new ArrayList<>(); Set> instances = createAllPossibleCodeComparisonPanels(); - // Put all panels in CodeComparisonPanel list. - // At same time, get a list of superseded panels. + // Put all panels in CodeComparisonPanel list; at same time, get a + // list of superseded panels. ArrayList>> classesOfPanelsToSupersede = new ArrayList<>(); for (CodeComparisonPanel codeComparisonPanel : instances) { @@ -175,545 +748,4 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener { } return instances; } - - /** - * Load the given functions into the views of this panel. - * - * @param newLeftFunction The function for the left side of the panel - * @param newRightFunction The function for the right side of the panel - */ - public void loadFunctions(Function newLeftFunction, Function newRightFunction) { - if (leftFunction != null && leftFunction.equals(newLeftFunction) && rightFunction != null && - rightFunction.equals(newRightFunction)) { - return; // already showing - } - - leftData = null; - rightData = null; - leftFunction = newLeftFunction; - rightFunction = newRightFunction; - leftProgram = (leftFunction != null) ? leftFunction.getProgram() : null; - rightProgram = (rightFunction != null) ? rightFunction.getProgram() : null; - leftAddressSet = (leftFunction != null) ? leftFunction.getBody() : new AddressSet(); - rightAddressSet = (rightFunction != null) ? rightFunction.getBody() : new AddressSet(); - - CodeComparisonPanel activePanel = - getActiveComparisonPanel(); - if (activePanel != null) { - activePanel.loadFunctions(leftFunction, rightFunction); - } - } - - /** - * Load the given data into the views of this panel. - * - * @param newLeftData The data for the left side of the panel - * @param newRightData The data for the right side of the panel - */ - public void loadData(Data newLeftData, Data newRightData) { - if (leftData != null && leftData.equals(newLeftData) && rightData != null && - rightData.equals(newRightData)) { - return; // already showing - } - - this.leftFunction = null; - this.rightFunction = null; - this.leftData = newLeftData; - this.rightData = newRightData; - leftProgram = (leftData != null) ? leftData.getProgram() : null; - rightProgram = (rightData != null) ? rightData.getProgram() : null; - - leftAddressSet = - (leftData != null) ? new AddressSet(leftData.getMinAddress(), leftData.getMaxAddress()) - : new AddressSet(); - rightAddressSet = (rightData != null) - ? new AddressSet(rightData.getMinAddress(), rightData.getMaxAddress()) - : new AddressSet(); - - CodeComparisonPanel activePanel = - getActiveComparisonPanel(); - if (activePanel != null) { - activePanel.loadData(leftData, rightData); - } - } - - /** - * Load info for the the given addresses of the indicated programs into the views of this panel. - * - * @param newLeftProgram the program for the left side of the panel - * @param newRightProgram the program for the right side of the panel - * @param newLeftAddresses addresses for the info to display in the left side of the panel - * @param newRightAddresses addresses for the info to display in the right side of the panel - */ - public void loadAddresses(Program newLeftProgram, Program newRightProgram, - AddressSetView newLeftAddresses, AddressSetView newRightAddresses) { - leftData = null; - rightData = null; - leftFunction = null; - rightFunction = null; - - leftProgram = newLeftProgram; - rightProgram = newRightProgram; - leftAddressSet = new AddressSet(newLeftAddresses); // AddressSet constructor handles null. - rightAddressSet = new AddressSet(newRightAddresses); // AddressSet constructor handles null. - CodeComparisonPanel activePanel = - getActiveComparisonPanel(); - if (activePanel != null) { - activePanel.loadAddresses(leftProgram, rightProgram, leftAddressSet, rightAddressSet); - } - } - - /** - * clear both sides of this panel. - */ - public void clear() { - leftData = null; - rightData = null; - leftFunction = null; - rightFunction = null; - - leftAddressSet = new AddressSet(); // AddressSet constructor handles null. - rightAddressSet = new AddressSet(); // AddressSet constructor handles null. - CodeComparisonPanel activePanel = - getActiveComparisonPanel(); - if (activePanel != null) { - activePanel.loadAddresses(null, null, null, null); - } - } - - /** - * Gets the ListingCodeComparisonPanel being displayed by this panel if one exists. - * @return the ListingCodeComparisonPanel or null - */ - public ListingCodeComparisonPanel getDualListingPanel() { - for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { - JComponent component = codeComparisonPanel.getComponent(); - if (component instanceof ListingCodeComparisonPanel) { - return (ListingCodeComparisonPanel) component; - } - } - return null; - } - - /** - * Invoked when the target of the listener has changed its state. In - * this case, the method is called when the user switches to another - * tab in the tabbed pane. - * - * @param e a ChangeEvent object - */ - @Override - public void stateChanged(ChangeEvent e) { - tabChanged(); - } - - boolean isTabClick(MouseEvent event) { - Component component = event.getComponent(); - int tabCount = tabbedPane.getTabCount(); - for (int i = 0; i < tabCount; i++) { - DockingTabRenderer renderer = (DockingTabRenderer) tabbedPane.getTabComponentAt(i); - if (SwingUtilities.isDescendingFrom(component, renderer)) { - return true; - } - } - return false; - } - - /** - * Set the current tabbed panel to be the component with the given name. - * @param name name of view to be current ( for example the "Listing View" ) - * @return true if the named view was found in the provider map - */ - public boolean setCurrentTabbedComponent(String name) { - - JComponent component = tabNameToComponentMap.get(name); - if (component != null) { - if (tabbedPane.getSelectedComponent() == component) { - tabChanged(); - } - tabbedPane.setSelectedComponent(component); // causes a state change event - } - return component != null; - } - - /** - * Get the current code comparison panel being viewed. - * - * @return null if there is no code comparison panel. - */ - CodeComparisonPanel getCurrentComponent() { - return (CodeComparisonPanel) tabbedPane.getSelectedComponent(); - } - - /** - * Get the name for the current code comparison panel being viewed. - * - * @return null if there is no code comparison panel. - */ - public String getCurrentComponentName() { - int selectedIndex = tabbedPane.getSelectedIndex(); - if (selectedIndex >= 0) { - return tabbedPane.getTitleAt(selectedIndex); - } - return null; - } - - /** - * Get the number of views in the tabbed pane. - */ - int getNumberOfTabbedComponents() { - return tabNameToComponentMap.size(); - } - - /** - * Remove all views in the tabbed pane. - */ - public void dispose() { - tool.removeComponentProvider(provider); - tabbedPane.removeAll(); - - setVisible(false); - for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { - codeComparisonPanel.dispose(); - } - } - - ///////////////////////////////////////////////////////////////////////// - // ** private methods ** - ///////////////////////////////////////////////////////////////////////// - /** - * Create the tabbed pane. - */ - private void create() { - tabbedPane = new JTabbedPane(); - - tabbedPane.addChangeListener(this); - setLayout(new BorderLayout()); - - add(tabbedPane, BorderLayout.CENTER); - setPreferredSize(new Dimension(200, 300)); - - leftProgram = (leftFunction != null) ? leftFunction.getProgram() : null; - rightProgram = (rightFunction != null) ? rightFunction.getProgram() : null; - - for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { - codeComparisonPanel.loadFunctions(leftFunction, rightFunction); - JComponent component = codeComparisonPanel.getComponent(); - addTab(codeComparisonPanel.getTitle(), component); - } - } - - private void addTab(String title, JComponent component) { - tabbedPane.add(title, component); - tabNameToComponentMap.put(title, component); - } - - /** - * If the panel is active, then set the current tab to be active - * and all others to be inactive. - */ - private void tabChanged() { - CodeComparisonPanel activePanel = - getActiveComparisonPanel(); - if (activePanel == null) { - return; // initializing - } - - if (leftFunction != null || rightFunction != null) { - activePanel.loadFunctions(leftFunction, rightFunction); - } - else if (leftData != null || rightData != null) { - activePanel.loadData(leftData, rightData); - } - else { - activePanel.loadAddresses(leftProgram, rightProgram, leftAddressSet, rightAddressSet); - } - } - - private CodeComparisonPanel getActiveComparisonPanel() { - JComponent c = (JComponent) tabbedPane.getSelectedComponent(); - for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { - JComponent component = codeComparisonPanel.getComponent(); - if (c == component) { - return codeComparisonPanel; - } - } - - return null; - } - - /** - * Gets the functions currently displayed by this panel. The first function in the array is the - * left function and the second function is the right function. The value for the left or right - * function can be null. - * @return the functions displayed. - */ - public Function[] getFunctions() { - return new Function[] { leftFunction, rightFunction }; - } - - /** - * Gets the function currently displayed in the left side of this panel or null if no function - * is currently displayed. - * @return the left function or null - */ - public Function getLeftFunction() { - return leftFunction; - } - - /** - * Sets the function to display in the left side of this panel. - * @param function the function to display or null to clear the left side. - */ - protected void setLeftFunction(Function function) { - loadFunctions(function, rightFunction); - } - - /** - * Gets the function currently displayed in the right side of this panel or null if no function - * is currently displayed. - * @return the right function or null - */ - public Function getRightFunction() { - return rightFunction; - } - - /** - * Sets the function to display in the right side of this panel. - * @param function the function to display or null to clear the right side. - */ - protected void setRightFunction(Function function) { - loadFunctions(leftFunction, function); - } - - /** - * Gets the data displayed in the left side of this panel. - * @return the left data or null - */ - public Data getLeftData() { - return leftData; - } - - /** - * Gets the data displayed in the right side of this panel. - * @return the right data or null - */ - public Data getRightData() { - return rightData; - } - - /** - * Enables/disables mouse navigation for all the CodeComparisonPanels displayed by this panel. - * @param enabled true means to enable mouse navigation in the panels. - */ - public void setMouseNavigationEnabled(boolean enabled) { - for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { - codeComparisonPanel.setMouseNavigationEnabled(enabled); - } - } - - /** - * Sets up the FunctionComparisonPanel and which CodeComparisonPanel is currently - * displayed based on the specified saveState. - * @param prefix identifier to prepend to any save state names to make them unique. - * @param saveState the save state for retrieving information. - */ - public void readConfigState(String prefix, SaveState saveState) { - String currentTabView = - saveState.getString(prefix + COMPARISON_VIEW_DISPLAYED, DEFAULT_CODE_COMPARISON_VIEW); - setCurrentTabbedComponent(currentTabView); - setScrollingSyncState( - saveState.getBoolean(prefix + CODE_COMPARISON_LOCK_SCROLLING_TOGETHER, true)); - ListingCodeComparisonPanel dualListingPanel = getDualListingPanel(); - if (dualListingPanel != null) { - dualListingPanel.readConfigState(prefix, saveState); - } - } - - /** - * Saves the information to the save state about the FunctionComparisonPanel and - * which CodeComparisonPanel is currently displayed. - * @param prefix identifier to prepend to any save state names to make them unique. - * @param saveState the save state where the information gets written. - */ - public void writeConfigState(String prefix, SaveState saveState) { - String currentComponentName = getCurrentComponentName(); - if (currentComponentName != null) { - saveState.putString(prefix + COMPARISON_VIEW_DISPLAYED, getCurrentComponentName()); - } - saveState.putBoolean(prefix + CODE_COMPARISON_LOCK_SCROLLING_TOGETHER, isScrollingSynced()); - ListingCodeComparisonPanel dualListingPanel = getDualListingPanel(); - if (dualListingPanel != null) { - dualListingPanel.writeConfigState(prefix, saveState); - } - } - - /** - * Gets all actions for the FunctionComparisonPanel and all CodeComparisonPanels in this - * FunctionComparisonPanel. - * @return the code comparison actions - */ - public DockingAction[] getCodeComparisonActions() { - ArrayList dockingActionList = new ArrayList<>(); - // Get actions for this functionComparisonPanel - DockingAction[] functionComparisonActions = getActions(); - for (DockingAction dockingAction : functionComparisonActions) { - dockingActionList.add(dockingAction); - } - // Get actions for each CodeComparisonPanel - for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { - DockingAction[] actions = codeComparisonPanel.getActions(); - for (DockingAction dockingAction : actions) { - dockingActionList.add(dockingAction); - } - } - return dockingActionList.toArray(new DockingAction[dockingActionList.size()]); - } - - /** - * Comparator that lets CodeComparisonPanels be sorted based on their title names. - */ - private class CodeComparisonPanelComparator - implements Comparator> { - - private CodeComparisonPanelComparator() { - - } - - @Override - public int compare(CodeComparisonPanel o1, - CodeComparisonPanel o2) { - if (o1 == o2) { - return 0; - } - String title1 = o1.getTitle(); - String title2 = o2.getTitle(); - return title1.compareTo(title2); - } - } - - /** - * Gets the currently displayed CodeComparisonPanel. - * @return the current panel or null. - */ - public CodeComparisonPanel getDisplayedPanel() { - int selectedIndex = tabbedPane.getSelectedIndex(); - Component component = tabbedPane.getComponentAt(selectedIndex); - return (CodeComparisonPanel) component; - } - - /** - * Gets all CodeComparisonPanels that are part of this FunctionComparisonPanel that match - * the indicated class. - * @param clazz the class of CodeComparisonPanels to return. - * @return the panels that match the specified class. - */ - public List> getMatchingPanels( - Class> clazz) { - ArrayList> matchingPanels = - new ArrayList<>(); - Component[] components = tabbedPane.getComponents(); - for (Component component : components) { - if (clazz.isAssignableFrom(component.getClass())) { - matchingPanels.add((CodeComparisonPanel) component); - } - } - return matchingPanels; - } - - /** - * Sets the prefixes that are to be prepended to the title displayed for each side of - * each CodeComparisonPanel. - * @param leftTitlePrefix the prefix to prepend to the left titles. - * @param rightTitlePrefix the prefix to prepend to the right titles. - */ - public void setTitlePrefixes(String leftTitlePrefix, String rightTitlePrefix) { - Component[] components = tabbedPane.getComponents(); - for (Component component : components) { - if (component instanceof CodeComparisonPanel) { - ((CodeComparisonPanel) component).setTitlePrefixes(leftTitlePrefix, - rightTitlePrefix); - } - } - } - - public ActionContext getActionContext(MouseEvent event, ComponentProvider componentProvider) { - Object source = (event != null) ? event.getSource() : null; - Component sourceComponent = (source instanceof Component) ? (Component) source : null; - ListingCodeComparisonPanel dualListingPanel = getDualListingPanel(); - // Is the action being taken on the dual listing. - if (dualListingPanel != null && dualListingPanel.isAncestorOf(sourceComponent)) { - return dualListingPanel.getActionContext(event, componentProvider); - } - return null; - } - - /** - * Determines if the layouts of the views are synchronized with respect to scrolling and - * location. - * @return true if scrolling is synchronized between the two views. - */ - public final boolean isScrollingSynced() { - return syncScrolling; - } - - /** - * Sets whether or not scrolling is synchronized. - * @param syncScrolling true means synchronize scrolling and location between the two views. - */ - public void setScrollingSyncState(boolean syncScrolling) { - if (isScrollingSynced() == syncScrolling) { - return; - } - toggleScrollLockAction.setSelected(syncScrolling); - toggleScrollLockAction.setToolBarData(new ToolBarData( - syncScrolling ? SYNC_SCROLLING_ICON : UNSYNC_SCROLLING_ICON, SCROLLING_GROUP)); - // Notify each comparison panel of the scrolling sync state. - for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { - codeComparisonPanel.setScrollingSyncState(syncScrolling); - } - this.syncScrolling = syncScrolling; - } - - private void createActions() { - toggleScrollLockAction = new ToggleScrollLockAction(); - } - - /** - * Get the actions for this FunctionComparisonPanel. - * @return an array containing the actions - */ - public DockingAction[] getActions() { - DockingAction[] actions = new DockingAction[] { toggleScrollLockAction }; - return actions; - } - - /** - * Updates the enablement for all actions provided by each panel. - */ - public void updateActionEnablement() { - for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { - codeComparisonPanel.updateActionEnablement(); - } - } - - class ToggleScrollLockAction extends ToggleDockingAction { - ToggleScrollLockAction() { - super("Synchronize Scrolling of Dual View", provider.getName()); - setDescription("Lock/Unlock Synchronized Scrolling of Dual View"); - setToolBarData(new ToolBarData(UNSYNC_SCROLLING_ICON, SCROLLING_GROUP)); - setEnabled(true); - MenuData menuData = - new MenuData(new String[] { "Synchronize Scrolling" }, DUAL_SCROLLING_ACTION_GROUP); - setMenuBarData(menuData); - - setHelpLocation( - new HelpLocation(DUAL_SCROLLING_HELP_TOPIC, "Synchronize Scrolling of Dual View")); - } - - @Override - public void actionPerformed(ActionContext context) { - setScrollingSyncState(isSelected()); - } - } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonPlugin.java index 66f60940bc..b86b51b4e8 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonPlugin.java @@ -15,20 +15,32 @@ */ package ghidra.app.plugin.core.functioncompare; +import java.util.Set; + +import docking.ComponentProviderActivationListener; import ghidra.app.CorePluginPackage; -import ghidra.app.events.ProgramClosedPluginEvent; +import ghidra.app.events.*; import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.plugin.ProgramPlugin; +import ghidra.app.plugin.core.functioncompare.actions.CompareFunctionsAction; +import ghidra.app.plugin.core.functioncompare.actions.CompareFunctionsFromListingAction; +import ghidra.app.services.FunctionComparisonService; import ghidra.framework.model.*; import ghidra.framework.plugintool.PluginInfo; import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.util.PluginStatus; import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Program; +import ghidra.program.util.ChangeManager; +import ghidra.program.util.ProgramChangeRecord; /** - * Plugin that provides the actions that allow the user to compare functions using a - * FunctionComparisonPanel. + * Allows users to create function comparisons that are displayed + * side-by-side in a provider. Comparisons can be initiated via the listing + * or function table and are displayed in a {@link FunctionComparisonProvider}. + *

+ * The underlying data backing the comparison provider is managed by the + * {@link FunctionComparisonService}. */ //@formatter:off @PluginInfo( @@ -36,36 +48,36 @@ import ghidra.program.model.listing.Program; packageName = CorePluginPackage.NAME, category = PluginCategoryNames.DIFF, shortDescription = "Compare Functions", - description = "This plugin provides actions that allow you to compare two or more functions with each other.", - eventsConsumed = { ProgramClosedPluginEvent.class } + description = "Allows users to compare two or more functions", + servicesProvided = { FunctionComparisonService.class }, + eventsConsumed = { ProgramSelectionPluginEvent.class, ProgramActivatedPluginEvent.class, + ProgramClosedPluginEvent.class } ) //@formatter:on -public class FunctionComparisonPlugin extends ProgramPlugin implements DomainObjectListener { +public class FunctionComparisonPlugin extends ProgramPlugin + implements DomainObjectListener, FunctionComparisonService { public final static String FUNCTION_MENU_SUBGROUP = "Function"; static final String MENU_PULLRIGHT = "CompareFunctions"; static final String POPUP_MENU_GROUP = "CompareFunction"; + private FunctionComparisonProviderManager functionComparisonManager; /** - * Creates a plugin that provides actions for comparing functions. - * @param tool the tool that owns this plugin. + * Constructor + * + * @param tool the tool that owns this plugin */ public FunctionComparisonPlugin(PluginTool tool) { super(tool, true, true); - functionComparisonManager = new FunctionComparisonProviderManager(this); - tool.setMenuGroup(new String[] { MENU_PULLRIGHT }, POPUP_MENU_GROUP); } @Override protected void init() { - createActions(); - } - - private void createActions() { - tool.addAction(new CompareFunctionsAction(this)); + CompareFunctionsAction compareFunctionsAction = new CompareFunctionsFromListingAction(tool); + tool.addAction(compareFunctionsAction); } @Override @@ -85,18 +97,69 @@ public class FunctionComparisonPlugin extends ProgramPlugin implements DomainObj } /** - * Displays a panel for comparing the specified functions. - * @param functions the functions that are used to populate both the left and right side - * of the function comparison panel. + * Overridden to listen for two event types: + *

  • Object Restored: In the event of a redo/undo that affects a function + * being shown in the comparison provider, this will allow tell the provider + * to reload
  • + *
  • Object Removed: If a function is deleted, this will tell the provider + * to purge it from the view
  • */ - void showFunctionComparisonProvider(Function[] functions) { - functionComparisonManager.showFunctionComparisonProvider(functions); + @Override + public void domainObjectChanged(DomainObjectChangedEvent ev) { + for (int i = 0; i < ev.numRecords(); ++i) { + DomainObjectChangeRecord doRecord = ev.getChangeRecord(i); + + int eventType = doRecord.getEventType(); + + switch (eventType) { + case DomainObject.DO_OBJECT_RESTORED: + functionComparisonManager.domainObjectRestored(ev); + break; + case ChangeManager.DOCR_FUNCTION_REMOVED: + ProgramChangeRecord rec = (ProgramChangeRecord) ev.getChangeRecord(i); + Function function = (Function) rec.getObject(); + if (function != null) { + removeFunction(function); + } + break; + } + } } @Override - public void domainObjectChanged(DomainObjectChangedEvent ev) { - if (ev.containsEvent(DomainObject.DO_OBJECT_RESTORED)) { - functionComparisonManager.domainObjectRestored(ev); - } + public void addFunctionComparisonProviderListener( + ComponentProviderActivationListener listener) { + functionComparisonManager.addProviderListener(listener); + } + + @Override + public void removeFunction(Function function) { + functionComparisonManager.removeFunction(function); + } + + @Override + public void removeFunction(Function function, FunctionComparisonProvider provider) { + functionComparisonManager.removeFunction(function, provider); + } + + @Override + public FunctionComparisonProvider compareFunctions(Function source, Function target) { + return functionComparisonManager.compareFunctions(source, target); + } + + @Override + public void compareFunctions(Set functions, FunctionComparisonProvider provider) { + functionComparisonManager.compareFunctions(functions, provider); + } + + @Override + public FunctionComparisonProvider compareFunctions(Set functions) { + return functionComparisonManager.compareFunctions(functions); + } + + @Override + public void compareFunctions(Function source, Function target, + FunctionComparisonProvider provider) { + functionComparisonManager.compareFunctions(source, target, provider); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonProvider.java index 53aafbc6eb..e513952bff 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonProvider.java @@ -18,140 +18,75 @@ package ghidra.app.plugin.core.functioncompare; import java.awt.event.MouseEvent; import java.util.*; -import javax.swing.Icon; - import docking.ActionContext; import docking.DockingTool; import docking.action.DockingAction; import docking.action.DockingActionIf; import docking.actions.PopupActionProvider; import docking.widgets.fieldpanel.internal.FieldPanelCoordinator; +import ghidra.app.services.FunctionComparisonModel; +import ghidra.app.services.FunctionComparisonService; import ghidra.app.util.viewer.listingpanel.ListingCodeComparisonPanel; import ghidra.app.util.viewer.listingpanel.ListingPanel; import ghidra.app.util.viewer.util.CodeComparisonPanel; import ghidra.framework.options.SaveState; -import ghidra.framework.plugintool.*; +import ghidra.framework.plugintool.ComponentProviderAdapter; +import ghidra.framework.plugintool.Plugin; import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Program; import ghidra.util.HelpLocation; -import resources.ResourceManager; /** - * This is the dockable provider that displays a FunctionComparisonPanel. + * Dockable provider that displays function comparisons Clients create/modify + * these comparisons using the {@link FunctionComparisonService}, which in turn + * creates instances of this provider as-needed. */ -public class FunctionComparisonProvider extends ComponentProviderAdapter implements PopupActionProvider { +public class FunctionComparisonProvider extends ComponentProviderAdapter + implements PopupActionProvider, FunctionComparisonModelListener { - private static final String HELP_TOPIC = "FunctionComparison"; - private static final Icon ICON = ResourceManager.loadImage("images/page_white_c.png"); - private FunctionComparisonPanel functionComparisonPanel; - private FunctionComparisonProviderListener listener; + protected static final String HELP_TOPIC = "FunctionComparison"; + protected FunctionComparisonPanel functionComparisonPanel; + protected Plugin plugin; + + /** Contains all the comparison data to be displayed by this provider */ + protected FunctionComparisonModel model; /** - * Creates a provider for displaying a FunctionComparisonPanel that allows two or more - * functions to be compared. This constructor will load the functions so they are available - * for display in both the left side and right side of the panel. By default the first function - * will be loaded into the left side and the second function will be loaded in the right side. - * @param plugin the plugin that owns this provider. - * @param functions the functions that are used to populate both the left and right side - * of the function comparison panel. - * @param listener the listener to notify when the provider is closing. + * Constructor + * + * @param plugin the active plugin + * @param name the providers name; used to group similar providers into a tab within + * the same window + * @param owner the provider owner, usually a plugin name */ - public FunctionComparisonProvider(Plugin plugin, Function[] functions, - FunctionComparisonProviderListener listener) { - super(plugin.getTool(), "Function Comparison", plugin.getName()); - this.listener = listener; - if (ICON != null) { - setIcon(ICON); - } - functionComparisonPanel = new MultiFunctionComparisonPanel(this, tool, functions); + public FunctionComparisonProvider(Plugin plugin, String name, String owner) { + this(plugin, name, owner, null); + } + + /** + * Constructor + * + * @param plugin the active plugin + * @param name the providers name; used to group similar providers into a tab within + * the same window + * @param owner the provider owner, usually a plugin name + * @param contextType the type of context supported by this provider; may be null + */ + public FunctionComparisonProvider(Plugin plugin, String name, String owner, + Class contextType) { + super(plugin.getTool(), name, owner, contextType); + this.plugin = plugin; + model = new FunctionComparisonModel(); + model.addFunctionComparisonModelListener(this); + functionComparisonPanel = getComponent(); initFunctionComparisonPanel(); } - /** - * Creates a provider for displaying two or more functions to be compared. This will load the - * functions so the leftFunctions are available for display in the left side and the - * rightFunctions are available for display in the right side of the function comparison panel. - * By default the first function from each array will be the one initially displayed in its - * associated side. - * @param plugin the plugin that owns this provider. - * @param leftFunctions the functions that are used to populate the left side - * @param rightFunctions the functions that are used to populate the right side - * @param listener the listener to notify when the provider is closing. - */ - public FunctionComparisonProvider(Plugin plugin, Function[] leftFunctions, - Function[] rightFunctions, FunctionComparisonProviderListener listener) { - super(plugin.getTool(), "FunctionComparison", plugin.getName()); - this.listener = listener; - if (ICON != null) { - setIcon(ICON); - } - functionComparisonPanel = - new MultiFunctionComparisonPanel(this, tool, leftFunctions, rightFunctions); - initFunctionComparisonPanel(); - } - - /** - * Creates a provider for displaying two or more functions to be compared. This will load - * the functions so the leftFunctions are available for display in the left side. For - * each left side function there is a list of right side functions for comparison. - * rightFunctions are available for display in the right side of the function comparison panel. - * By default the first function from each array will be the one initially displayed in its - * associated side. - * @param plugin the plugin that owns this provider. - * @param functionMap maps each left function to its own set of right functions for comparison. - * @param listener the listener to notify when the provider is closing. - */ - public FunctionComparisonProvider(Plugin plugin, - HashMap> functionMap, - FunctionComparisonProviderListener listener) { - - super(plugin.getTool(), "FunctionComparison", plugin.getName()); - this.listener = listener; - if (ICON != null) { - setIcon(ICON); - } - functionComparisonPanel = new MappedFunctionComparisonPanel(this, tool, functionMap); - initFunctionComparisonPanel(); - } - - /** - * Perform initialization for this provider and its panel. This includes setting the - * tab text, getting actions, establishing the popup listener, and specifying help. - */ - private void initFunctionComparisonPanel() { - setTransient(); - setTabText(functionComparisonPanel); - addSpecificCodeComparisonActions(); - tool.addPopupActionProvider(this); - setHelpLocation(new HelpLocation(HELP_TOPIC, "Function Comparison")); - } - - /** - * Creates the text that is displayed on the tab for this provider. - * @param functionCompPanel the function comparison panel for this provider. - */ - private void setTabText(FunctionComparisonPanel functionCompPanel) { - Function leftFunction = functionCompPanel.getLeftFunction(); - Function rightFunction = functionCompPanel.getRightFunction(); - String tabText = (leftFunction == null && rightFunction == null) ? "No Functions Yet" - : getTabText(leftFunction, rightFunction); - setTabText(tabText); - } - - private String getTabText(Function function1, Function function2) { - return ((function1 != null) ? function1.getName() : "none") + " & " + - ((function2 != null) ? function2.getName() : "none"); - } - - private void addSpecificCodeComparisonActions() { - DockingAction[] actions = functionComparisonPanel.getCodeComparisonActions(); - for (DockingAction dockingAction : actions) { - addLocalAction(dockingAction); - } - } - @Override public FunctionComparisonPanel getComponent() { + if (functionComparisonPanel == null) { + functionComparisonPanel = new FunctionComparisonPanel(this, tool, null, null); + } return functionComparisonPanel; } @@ -161,6 +96,8 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter impleme buff.append("FunctionComparisonProvider\n"); buff.append("Name: "); buff.append(getName() + "\n"); + buff.append("Tab Text: "); + buff.append(getTabText() + "\n"); Function leftFunction = functionComparisonPanel.getLeftFunction(); String leftName = (leftFunction != null) ? leftFunction.getName() : "No Function"; buff.append("Function 1: " + leftName + "\n"); @@ -181,44 +118,15 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter impleme @Override public void removeFromTool() { tool.removePopupActionProvider(this); - super.removeFromTool(); } @Override - public void closeComponent() { - super.closeComponent(); - - if (listener != null) { - listener.providerClosed(this); - } - } - - /** - * Indicates that the specified program has been closed, so this provider can close its - * component, if any of its functions were from that program. - * @param program the program that was closed. - */ - public void programClosed(Program program) { - // For now close the panel if it has any functions with this program. - Function[] functions = functionComparisonPanel.getFunctions(); - for (Function function : functions) { - if ((function != null) && function.getProgram() == program) { - closeComponent(); - return; - } - } - } - - /** - * Indicates that the specified program has been restored, so this can refresh the code - * comparison panel. - * @param program the program that was restored (undo/redo). - */ - public void programRestored(Program program) { - CodeComparisonPanel comparePanel = - functionComparisonPanel.getCurrentComponent(); - comparePanel.programRestored(program); + public void modelChanged(List model) { + this.model.setComparisons(model); + functionComparisonPanel.reload(); + setTabText(functionComparisonPanel); + closeIfEmpty(); } @Override @@ -235,7 +143,72 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter impleme } /** - * Restores the function comparison provider's components to the indicated saved configuration state. + * Returns the comparison model + * + * @return the comparison model + */ + public FunctionComparisonModel getModel() { + return model; + } + + /** + * Replaces the comparison model with the one provided + * + * @param model the comparison model + */ + public void setModel(FunctionComparisonModel model) { + this.model = model; + } + + /** + * Removes any functions being displayed by this provider that are from + * the given program. If there are no functions left to display, the + * provider is closed. + * + * @param program the program being closed + */ + public void programClosed(Program program) { + model.removeFunctions(program); + closeIfEmpty(); + } + + /** + * Removes all functions for the specified program from the comparison + * model + * + * @param program the program whose functions require removal + */ + public void removeFunctions(Program program) { + model.removeFunctions(program); + closeIfEmpty(); + } + + /** + * Removes the set of functions from the comparison model + * + * @param functions the functions to remove + */ + public void removeFunctions(Set functions) { + functions.stream().forEach(f -> model.removeFunction(f)); + closeIfEmpty(); + } + + /** + * Indicates that the specified program has been restored, so the + * comparison panel should be refreshed + * + * @param program the program that was restored (undo/redo) + */ + public void programRestored(Program program) { + CodeComparisonPanel comparePanel = + functionComparisonPanel.getCurrentComponent(); + comparePanel.programRestored(program); + } + + /** + * Restores the function comparison providers components to the indicated + * saved configuration state + * * @param saveState the configuration state to restore */ public void readConfigState(SaveState saveState) { @@ -243,10 +216,78 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter impleme } /** - * Saves the current configuration state of the components that compose the function comparison provider. + * Saves the current configuration state of the components that compose + * the function comparison provider + * * @param saveState the new configuration state */ public void writeConfigState(SaveState saveState) { functionComparisonPanel.writeConfigState(getName(), saveState); } + + /** + * Creates the text that is displayed on the tab for this provider + * + * @param comparisonPanel the function comparison panel for this provider + */ + public void setTabText(FunctionComparisonPanel comparisonPanel) { + Function leftFunction = comparisonPanel.getLeftFunction(); + Function rightFunction = comparisonPanel.getRightFunction(); + String tabText = (leftFunction == null && rightFunction == null) ? "No Functions Yet" + : getTabText(leftFunction, rightFunction); + setTabText(tabText); + } + + /** + * Perform initialization for this provider and its panel + */ + protected void initFunctionComparisonPanel() { + setTransient(); + setTabText(functionComparisonPanel); + addSpecificCodeComparisonActions(); + tool.addPopupActionProvider(this); + setHelpLocation(new HelpLocation(HELP_TOPIC, "Function Comparison")); + } + + /** + * Returns true if the comparison panel is empty + * + * @return true if the panel is empty + */ + boolean isEmpty() { + return functionComparisonPanel.isEmpty(); + } + + /** + * Closes this provider if there are no comparisons to view + */ + void closeIfEmpty() { + if (isEmpty()) { + closeComponent(); + } + } + + /** + * Returns the text that should be displayed in the tab, given the two + * left/right functions + * + * @param leftFunction + * @param rightFunction + * @return the tab text + */ + private String getTabText(Function leftFunction, Function rightFunction) { + return ((leftFunction != null) ? leftFunction.getName() : "none") + " & " + + ((rightFunction != null) ? rightFunction.getName() : "none"); + } + + /** + * Gets actions specific to the code comparison panel and adds them to this + * provider + */ + private void addSpecificCodeComparisonActions() { + DockingAction[] actions = functionComparisonPanel.getCodeComparisonActions(); + for (DockingAction dockingAction : actions) { + addLocalAction(dockingAction); + } + } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonProviderListener.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonProviderListener.java index 2058107afc..449b9a5f87 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonProviderListener.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonProviderListener.java @@ -16,13 +16,22 @@ package ghidra.app.plugin.core.functioncompare; /** - * Listener for a FunctionComparisonProvider. + * Allows subscribers to register for function comparison provider changes + * (eg: when the provider is opened/closed) */ public interface FunctionComparisonProviderListener { /** - * Notification method that will get called when the provider is being closed. - * @param provider the provider that is being closed. + * Invoked when the provider is being closed + * + * @param provider the closed provider */ public void providerClosed(FunctionComparisonProvider provider); + + /** + * Invoked when the provider is being opened + * + * @param provider the opened provider + */ + public void providerOpened(FunctionComparisonProvider provider); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonProviderManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonProviderManager.java index 7fcf38eddd..f8aff61543 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonProviderManager.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonProviderManager.java @@ -15,219 +15,199 @@ */ package ghidra.app.plugin.core.functioncompare; -import java.util.*; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; -import ghidra.app.plugin.ProgramPlugin; +import docking.ComponentProviderActivationListener; import ghidra.framework.model.*; -import ghidra.framework.plugintool.PluginTool; +import ghidra.framework.plugintool.Plugin; import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Program; /** - * FunctionComparisonProviderManager allows a plugin to display function comparison panels. - * It responds to program close events and closes any panels that have a function from the - * closed program. It also updates the displays if needed due to an Undo. + * Provides access to all open {@link FunctionComparisonProvider comparison providers} + * and allows users to do the following: + *
  • create new providers
  • + *
  • add comparisons to existing providers
  • + *
  • remove comparisons
  • + *
  • notify subscribers when providers are opened/closed
  • */ public class FunctionComparisonProviderManager implements FunctionComparisonProviderListener { - private HashSet functionComparisonProviders = new HashSet<>(); - private ProgramPlugin plugin; - private PluginTool tool; + private Set providers = new CopyOnWriteArraySet<>(); + private Set listeners = new HashSet<>(); + private Plugin plugin; /** - * Constructs a FunctionComparisonProviderManager. - * @param plugin the plugin that owns this manager. + * Constructor + * + * @param plugin the parent plugin */ - public FunctionComparisonProviderManager(ProgramPlugin plugin) { + public FunctionComparisonProviderManager(Plugin plugin) { this.plugin = plugin; - tool = plugin.getTool(); - } - - /** - * This will create a new function comparison panel with the specified functions available for - * display in both the left and right side of the panel. Initially the function comparison panel - * will display the first function in the left side and the second function in the right side of - * the panel. If the manager already has a provider to display the specified functions it will - * be brought to the front instead of creating a new panel. - * @param functions the functions that are used to populate both the left and right side - * of the function comparison panel. - * @return the FunctionComparisonProvider that is displaying these functions. - */ - public FunctionComparisonProvider showFunctionComparisonProvider(Function[] functions) { - FunctionComparisonProvider functionComparisonProvider = - findFunctionComparisonProvider(functions); - if (functionComparisonProvider != null) { - // If it is already displayed then bring it to the front. - tool.toFront(functionComparisonProvider); - return functionComparisonProvider; - } - FunctionComparisonProvider provider = - new FunctionComparisonProvider(plugin, functions, this); - functionComparisonProviders.add(provider); - provider.setVisible(true); - return provider; - } - - /** - * This creates a new function comparison panel with the specified leftFunctions available for - * display in the left side of the panel and the rightFunctions available for display in the right side. - * Initially the function comparison panel will display the first leftFunction and the first - * rightFunction. If the manager already has a provider to display the specified leftFunctions - * and rightFunctions it will be brought to the front instead of creating a new panel. - * @param leftFunctions the functions that are used to populate the left side - * @param rightFunctions the functions that are used to populate the right side - * @return the FunctionComparisonProvider that is displaying these functions. - */ - public FunctionComparisonProvider showFunctionComparisonProvider(Function[] leftFunctions, - Function[] rightFunctions) { - FunctionComparisonProvider functionComparisonProvider = - findFunctionComparisonProvider(leftFunctions, rightFunctions); - if (functionComparisonProvider != null) { - // If it is already displayed then bring it to the front. - tool.toFront(functionComparisonProvider); - return functionComparisonProvider; - } - FunctionComparisonProvider provider = - new FunctionComparisonProvider(plugin, leftFunctions, rightFunctions, this); - functionComparisonProviders.add(provider); - provider.setVisible(true); - return provider; - } - - /** - * This creates a new function comparison panel with the specified left functions - * available for display in the left side of the panel and a list of functions for the - * right side for each function in the left. - * If the manager already has a provider to display the specified function map it will - * be brought to the front instead of creating a new panel. - * @param functionMap map of the functions that are used to populate both the left and - * right side of the function comparison panel. - * @return the FunctionComparisonProvider that is displaying these functions. - */ - public FunctionComparisonProvider showFunctionComparisonProvider( - HashMap> functionMap) { - FunctionComparisonProvider functionComparisonProvider = - findFunctionComparisonProvider(functionMap); - if (functionComparisonProvider != null) { - // If it is already displayed then bring it to the front. - tool.toFront(functionComparisonProvider); - return functionComparisonProvider; - } - FunctionComparisonProvider provider = - new FunctionComparisonProvider(plugin, functionMap, this); - functionComparisonProviders.add(provider); - provider.setVisible(true); - return provider; - } - - private FunctionComparisonProvider findFunctionComparisonProvider(Function[] functions) { - for (FunctionComparisonProvider functionComparisonProvider : functionComparisonProviders) { - FunctionComparisonPanel functionComparisonPanel = - functionComparisonProvider.getComponent(); - if (functionComparisonPanel instanceof MultiFunctionComparisonPanel) { - MultiFunctionComparisonPanel multiPanel = - (MultiFunctionComparisonPanel) functionComparisonPanel; - if (multiPanel.matchesTheseFunctions(functions, functions)) { - return functionComparisonProvider; - } - } - else { // basic FunctionComparisonPanel - Function[] panelFunctions = functionComparisonPanel.getFunctions(); - if (Arrays.equals(panelFunctions, functions)) { - return functionComparisonProvider; - } - } - } - return null; - } - - private FunctionComparisonProvider findFunctionComparisonProvider(Function[] functionsL, - Function[] functionsR) { - for (FunctionComparisonProvider functionComparisonProvider : functionComparisonProviders) { - FunctionComparisonPanel functionComparisonPanel = - functionComparisonProvider.getComponent(); - if (!(functionComparisonPanel instanceof MultiFunctionComparisonPanel)) { - continue; - } - MultiFunctionComparisonPanel multiPanel = - (MultiFunctionComparisonPanel) functionComparisonPanel; - if (multiPanel.matchesTheseFunctions(functionsL, functionsR)) { - return functionComparisonProvider; - } - } - return null; - } - - private FunctionComparisonProvider findFunctionComparisonProvider( - HashMap> functionMap) { - - for (FunctionComparisonProvider functionComparisonProvider : functionComparisonProviders) { - FunctionComparisonPanel functionComparisonPanel = - functionComparisonProvider.getComponent(); - if (functionComparisonPanel instanceof MappedFunctionComparisonPanel) { - MappedFunctionComparisonPanel mappedPanel = - (MappedFunctionComparisonPanel) functionComparisonPanel; - if (mappedPanel.matchesTheseFunctions(functionMap)) { - return functionComparisonProvider; - } - } - } - return null; } @Override public void providerClosed(FunctionComparisonProvider provider) { - functionComparisonProviders.remove(provider); + providers.remove(provider); + listeners.stream().forEach(l -> l.componentProviderDeactivated(provider)); + } + + @Override + public void providerOpened(FunctionComparisonProvider provider) { + listeners.stream().forEach(l -> l.componentProviderActivated(provider)); } /** - * Closes all the function comparison providers that have a function from the indicated program. - * This method should be called when a program is closing. - * @param program the program whose function providers need to close. + * Creates a new comparison between the given set of functions + * + * @param functions the functions to compare + * @return the new comparison provider + */ + public FunctionComparisonProvider compareFunctions(Set functions) { + if (functions.isEmpty()) { + return null; + } + FunctionComparisonProvider provider = new MultiFunctionComparisonProvider(plugin); + provider.addToTool(); + provider.getModel().compareFunctions(functions); + providers.add(provider); + provider.setVisible(true); + return provider; + } + + /** + * Creates a new comparison comparison between two functions + * + * @param source the source function + * @param target the target function + * @return the new comparison provider + */ + public FunctionComparisonProvider compareFunctions(Function source, + Function target) { + FunctionComparisonProvider provider = new MultiFunctionComparisonProvider(plugin); + provider.addToTool(); + provider.getModel().compareFunctions(source, target); + providers.add(provider); + provider.setVisible(true); + return provider; + } + + /** + * Adds a set of functions to an existing comparison provider + * + * @param functions the functions to compare + * @param provider the provider to add the functions to + */ + public void compareFunctions(Set functions, FunctionComparisonProvider provider) { + if (functions.isEmpty() || provider == null) { + return; + } + + providers.add(provider); + provider.setVisible(true); + provider.getModel().compareFunctions(functions); + } + + /** + * Adds the given functions to an existing comparison provider + * + * @param source the source function + * @param target the target function + * @param provider the provider to add the functions to + */ + public void compareFunctions(Function source, Function target, + FunctionComparisonProvider provider) { + if (provider == null) { + return; + } + + providers.add(provider); + provider.setVisible(true); + provider.getModel().compareFunctions(source, target); + } + + /** + * Removes a given function from all comparisons across all providers + * + * @param function the function to remove + */ + public void removeFunction(Function function) { + providers.stream().forEach(p -> p.getModel().removeFunction(function)); + } + + /** + * Removes a given function from a specified provider + * + * @param function the function to remove + * @param provider the provider to remove the function from + */ + public void removeFunction(Function function, FunctionComparisonProvider provider) { + if (provider == null) { + return; + } + provider.getModel().removeFunction(function); + } + + /** + * Registers subscribers who wish to know of provider activation status + * + * @param listener the subscriber to register + */ + public void addProviderListener(ComponentProviderActivationListener listener) { + listeners.add(listener); + } + + /** + * Closes all the comparison providers that contain a function from + * the given program + * + * @param program the program whose function providers need to close */ public void closeProviders(Program program) { - // Get an array of the providers and loop over it to notify them. This is to prevent - // causing a ConcurrentModificationException. If a provider closes due to the indicated - // program closing, this manager will get notified via the providerClosed method and - // remove that provider from the functionComparisonProviders hashset. - FunctionComparisonProvider[] providers = functionComparisonProviders - .toArray(new FunctionComparisonProvider[functionComparisonProviders.size()]); - for (FunctionComparisonProvider functionComparisonProvider : providers) { - functionComparisonProvider.programClosed(program); // Allow the provider to close itself. - } + providers.stream().forEach(p -> p.programClosed(program)); } /** - * Cleans up since this manager is being disposed. All function comparison providers will - * close and be cleaned up. + * Removes any comparisons that contain a function from the given program + * + * @param program the program whose functions require removal + */ + public void removeFunctions(Program program) { + providers.stream().forEach(p -> p.removeFunctions(program)); + } + + /** + * Cleans up all providers, setting them invisible and removing any + * associated ui components (eg: tabs) */ public void dispose() { - for (FunctionComparisonProvider functionComparisonProvider : functionComparisonProviders) { - FunctionComparisonPanel functionComparisonPanel = - functionComparisonProvider.getComponent(); - functionComparisonPanel.setVisible(false); - functionComparisonPanel.dispose(); + for (FunctionComparisonProvider provider : providers) { + FunctionComparisonPanel panel = provider.getComponent(); + panel.setVisible(false); + panel.dispose(); } - functionComparisonProviders.clear(); + providers.clear(); } /** - * Called when there is an Undo/Redo. If a program is being restored, this will notify all the - * function comparison providers. This allows them to refresh if they are showing a function - * from the program. - * @param ev the event indicating if this is an Undo/Redo on a program. + * Called when there is an Undo/Redo. If a program is being restored, this + * will notify all the function comparison providers. This allows them to + * refresh if they are showing a function from the program + * + * @param ev the object changed event */ public void domainObjectRestored(DomainObjectChangedEvent ev) { for (DomainObjectChangeRecord domainObjectChangeRecord : ev) { int eventType = domainObjectChangeRecord.getEventType(); - if (eventType == DomainObject.DO_OBJECT_RESTORED) { - Object source = ev.getSource(); - if (source instanceof Program) { - Program program = (Program) source; - for (FunctionComparisonProvider functionComparisonProvider : functionComparisonProviders) { - functionComparisonProvider.programRestored(program); - } - } + if (eventType != DomainObject.DO_OBJECT_RESTORED) { + return; + } + Object source = ev.getSource(); + if (source instanceof Program) { + Program program = (Program) source; + providers.stream().forEach(p -> p.programRestored(program)); } } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/MappedFunctionComparisonPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/MappedFunctionComparisonPanel.java deleted file mode 100644 index c45d97b4ca..0000000000 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/MappedFunctionComparisonPanel.java +++ /dev/null @@ -1,167 +0,0 @@ -/* ### - * IP: GHIDRA - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ghidra.app.plugin.core.functioncompare; - -import java.util.*; - -import javax.swing.DefaultComboBoxModel; - -import docking.ComponentProvider; -import ghidra.framework.plugintool.PluginTool; -import ghidra.program.model.listing.Function; -import ghidra.util.HelpLocation; - -/** - * Creates a panel for comparing two or more functions. One or more functions can be displayed - * in the left side of the panel. Each of these left functions is mapped to its own set of functions, - * which can be displayed one at a time in the right side of the panel for comparison. - * If there are multiple functions to display within either the left or right side of this panel, - * then a combo box will appear above the left and right side of the CodeComparisonPanels. - * Each combo box will allow the user to choose which function to display on that side of the panel. - * Changing the selected function in the left side of the panel will possibly change the available - * functions for display in the right side of the panel. - */ -public class MappedFunctionComparisonPanel extends FunctionChoiceComparisonPanel { - - private HashMap> functionMap; - - /** - * Constructor - * - * @param provider the provider displaying this panel. - * @param tool the tool displaying this panel. - * @param functionMap map of the functions that are used to populate both the left and right side - * of the function comparison panel. - */ - public MappedFunctionComparisonPanel(ComponentProvider provider, PluginTool tool, - HashMap> functionMap) { - super(provider, tool, null, null); - this.functionMap = functionMap; - - establishLeftFunctions(0); - establishRightFunctions(0); - - if (leftWrappedFunctions.length > 1 || rightWrappedFunctions.length > 1) { - addChoicePanel(); - } - - reloadLeftFunctions(); - reloadRightFunctions(); - - createActions(); - help.registerHelp(this, new HelpLocation(HELP_TOPIC, "Function Comparison")); - } - - /** - * Sets the left functions that are used in the left combo box for the left side of the - * function comparison. These functions are in sorted order. It also sets the current - * left index to a valid value that indicates which function in the left list is currently - * selected. - * @param leftFunctionIndex the desired index of the left function that should be - * selected currently. If the specified index isn't valid for the current list of left - * functions then the left index will get set to 0 which indicates the first function. - */ - private void establishLeftFunctions(int leftFunctionIndex) { - Set leftFunctionSet = functionMap.keySet(); - Function[] leftSortedFunctions = getSortedFunctions(leftFunctionSet); - leftWrappedFunctions = getWrappedFunctions(leftSortedFunctions); - leftIndex = (leftFunctionIndex < leftSortedFunctions.length) ? leftFunctionIndex : 0; - setLeftFunction(leftSortedFunctions[leftIndex]); - } - - private void adjustRightFunctions(int rightFunctionIndex) { - establishRightFunctions(rightFunctionIndex); - reloadRightFunctions(); - } - - /** - * Sets the right functions that are used in the right combo box for the right side of the - * function comparison. These functions are in sorted order. It also sets the current - * right index to a valid value that indicates which function in the right list is currently - * selected. - * @param rightFunctionIndex the desired index of the right function that should be - * selected currently. If the specified index isn't valid for the current list of right - * functions then the right index will get set to 0 which indicates the first function. - */ - private void establishRightFunctions(int rightFunctionIndex) { - Set rightFunctionSet = functionMap.get(getLeftFunction()); - Function[] rightSortedFunctions = - (rightFunctionSet != null) ? getSortedFunctions(rightFunctionSet) : new Function[0]; - rightWrappedFunctions = getWrappedFunctions(rightSortedFunctions); - rightIndex = (rightFunctionIndex < rightSortedFunctions.length) ? rightFunctionIndex : 0; - } - - private void reloadLeftFunctions() { - // Adjust the index if it is out of bounds. - if (leftIndex >= leftWrappedFunctions.length) { - leftIndex = 0; - } - if (leftComboBox != null) { - // Load the functions into the combo box. - leftComboBox.setModel(new DefaultComboBoxModel<>(leftWrappedFunctions)); - // Select the function in the combo box. - adjustSelectedLeftFunction(); - } - // Set the function in the view. - Function leftFunctionAtIndex = (leftIndex < leftWrappedFunctions.length) - ? leftWrappedFunctions[leftIndex].getFunction() - : null; - setLeftFunction(leftFunctionAtIndex); - } - - private void reloadRightFunctions() { - // Adjust the index if it is out of bounds. - if (rightIndex >= rightWrappedFunctions.length) { - rightIndex = 0; - } - if (rightComboBox != null) { - // Load the functions into the combo box. - rightComboBox.setModel(new DefaultComboBoxModel<>(rightWrappedFunctions)); - // Select the function in the combo box. - adjustSelectedRightFunction(); - } - // Set the function in the view. - Function rightFunctionAtIndex = (rightIndex < rightWrappedFunctions.length) - ? rightWrappedFunctions[rightIndex].getFunction() - : null; - setRightFunction(rightFunctionAtIndex); - } - - /** - * Determines if the map of left functions to lists of associated right functions match - * the map of functions currently displayed for comparison in the left and right side of - * this panel. - * - * @param functionMap the map of left functions to lists of right functions - * @return true if the map matches what is currently displayed by this panel. - */ - boolean matchesTheseFunctions(HashMap> myFunctionMap) { - return functionMap.equals(myFunctionMap); - } - - @Override - public void loadFunctions(Function newLeftFunction, Function newRightFunction) { - Function myLeftFunction = getLeftFunction(); - - super.loadFunctions(newLeftFunction, newRightFunction); - - if (myLeftFunction != getLeftFunction()) { - // Left function changed so adjust the function list in the right side. - adjustRightFunctions(0); - } - } - -} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/MultiFunctionComparisonPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/MultiFunctionComparisonPanel.java index f6faa2c4d3..aa85403050 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/MultiFunctionComparisonPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/MultiFunctionComparisonPanel.java @@ -15,109 +15,298 @@ */ package ghidra.app.plugin.core.functioncompare; -import java.util.Arrays; +import java.awt.*; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.util.Iterator; +import java.util.Set; -import docking.ComponentProvider; +import javax.swing.*; + +import docking.help.Help; +import docking.help.HelpService; +import docking.widgets.fieldpanel.internal.FieldPanelCoordinator; +import ghidra.app.services.FunctionComparisonModel; +import ghidra.app.util.viewer.util.CodeComparisonPanel; import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.listing.Function; -import ghidra.util.HelpLocation; /** - * Creates a panel for comparing two or more functions. - * If there are multiple functions to display within either the left or right side of this panel, - * then a combo box will appear above the left and right side of the CodeComparisonPanels. - * Each combo box will allow the user to choose which function to display on that side of the panel. + * Extends the basic {@link FunctionComparisonPanel one-to-one comparison panel} + * to allow a many-to-many relationship. The panel provides a pair of combo + * boxes above the function display area that allows users to select which + * functions are to be compared. + *

    + * Throughout this class the terms source and target + * are used when referencing functions. This is because the model that backs + * this panel maintains a relationship between the functions being compared + * such that each source function can only be compared to a specific set + * of target functions. For all practical purposes, the source functions + * appear in the left-side panel and targets appear on the right. + * */ -public class MultiFunctionComparisonPanel extends FunctionChoiceComparisonPanel { +public class MultiFunctionComparisonPanel extends FunctionComparisonPanel { + + /** Functions that will show up on the left side of the panel */ + private JComboBox sourceFunctionsCB; + + /** Functions that will show up on the right side of the panel */ + private JComboBox targetFunctionsCB; + + /** Data models backing the source and target combo boxes */ + private DefaultComboBoxModel sourceFunctionsCBModel; + private DefaultComboBoxModel targetFunctionsCBModel; + + protected static final HelpService help = Help.getHelpService(); + public static final String HELP_TOPIC = "FunctionComparison"; /** - * Creates a panel for displaying two or more functions to be compared. This makes the - * functions available for display in both the left side and right side of the panel after - * they are sorted ascending based on the program and function. The primary sort is on - * program including pathname. The secondary sort is on function including namespace. - * By default the first function will be loaded into the left side and the second function - * will be loaded in the right side. - * @param provider the provider displaying this panel. - * @param tool the tool displaying this panel. - * @param functions the functions that are used to populate both the left and right side - * of the function comparison panel. - */ - public MultiFunctionComparisonPanel(ComponentProvider provider, PluginTool tool, - Function[] functions) { - super(provider, tool, null, null); - // For now, sort the functions. - Function[] sortedFunctions = getSortedFunctions(functions); - leftWrappedFunctions = getWrappedFunctions(sortedFunctions); - rightWrappedFunctions = leftWrappedFunctions; - if (leftWrappedFunctions.length >= 2) { - Function leftFunction = (leftIndex < leftWrappedFunctions.length) - ? leftWrappedFunctions[leftIndex].getFunction() - : null; - ++rightIndex; - Function rightFunction = (rightIndex < rightWrappedFunctions.length) - ? rightWrappedFunctions[rightIndex].getFunction() - : null; - loadFunctions(leftFunction, rightFunction); - } - // Don't include the choice panel with its combo boxes unless there are more than 2 functions. - if (leftWrappedFunctions.length > 2) { - addChoicePanel(); // This also populates the combo boxes. - } - createActions(); - help.registerHelp(this, new HelpLocation(HELP_TOPIC, "Function Comparison")); - } - - /** - * Creates a panel for displaying two or more functions to be compared. This will load the - * functions so the leftFunctions are available for display in the left side and the - * rightFunctions are available for display in the right side of the function comparison panel. - * The functions are sorted ascending based on the program and function. The primary sort - * is on program including pathname. The secondary sort is on function including namespace. - * By default the first function from each array will be the one initially displayed in its - * associated side. - * @param provider the provider displaying this panel. - * @param tool the tool displaying this panel. - * @param leftFunctions the functions that are used to populate the left side - * @param rightFunctions the functions that are used to populate the right side - */ - public MultiFunctionComparisonPanel(ComponentProvider provider, PluginTool tool, - Function[] leftFunctions, Function[] rightFunctions) { - super(provider, tool, null, null); - Function[] sortedLeftFunctions = getSortedFunctions(leftFunctions); - Function[] sortedRightFunctions = getSortedFunctions(rightFunctions); - leftWrappedFunctions = getWrappedFunctions(sortedLeftFunctions); - rightWrappedFunctions = - Arrays.equals(sortedLeftFunctions, sortedRightFunctions) ? leftWrappedFunctions - : getWrappedFunctions(sortedRightFunctions); - if ((leftWrappedFunctions.length >= 1) && (rightWrappedFunctions.length >= 1)) { - Function leftFunction = (leftIndex < leftWrappedFunctions.length) - ? leftWrappedFunctions[leftIndex].getFunction() - : null; // Initially leftIndex is 0. - Function rightFunction = (rightIndex < rightWrappedFunctions.length) - ? rightWrappedFunctions[rightIndex].getFunction() - : null; // Initially rightIndex is 0. - if (leftFunction == rightFunction && rightWrappedFunctions.length > 1) { - rightFunction = rightWrappedFunctions[++rightIndex].getFunction(); - } - loadFunctions(leftFunction, rightFunction); - } - if (leftWrappedFunctions.length > 1 || rightWrappedFunctions.length > 1) { - addChoicePanel(); // This also populates the combo boxes. - } - createActions(); - } - - /** - * Determines if functionsL and functionsR match the functions - * that can be displayed for comparison in the left and right side of this panel. + * Constructor * - * @param functionsL the functions to check against those used to populate the left side - * @param functionsR the functions to check against those used to populate the right side - * @return true if functionsL and functionsR match the functions that can be displayed by - * this panel. + * @param provider the comparison provider associated with this panel + * @param tool the active plugin tool */ - boolean matchesTheseFunctions(Function[] functionsL, Function[] functionsR) { - return Arrays.equals(getLeftFunctions(), getSortedFunctions(functionsL)) && - Arrays.equals(getRightFunctions(), getSortedFunctions(functionsR)); + public MultiFunctionComparisonPanel(FunctionComparisonProvider provider, + PluginTool tool) { + super(provider, tool, null, null); + + JPanel choicePanel = new JPanel(new GridLayout(1, 2)); + choicePanel.add(createSourcePanel()); + choicePanel.add(createTargetPanel()); + add(choicePanel, BorderLayout.NORTH); + + // For the multi-panels we don't need to show the title of each + // comparison panel because the name of the function/data being shown + // is already visible in the combo box + getComparisonPanels().forEach(p -> p.setShowTitles(false)); + } + + /** + * Clears the given functions from the comparison panel (both the + * source and target lists) (the default for this method is to only clear + * out the functions visible in the left/right panels) + */ + @Override + public void removeFunctions(Set functions) { + ((MultiFunctionComparisonProvider) provider).removeFunctions(functions); + } + + /** + * Clears out the source and targets lists and reloads them to + * ensure that they reflect the current state of the data model. Any + * currently-selected list items will be restored after the lists + * are reloaded. + */ + @Override + public void reload() { + SwingUtilities.invokeLater(() -> { + reloadSourceList(); + Function selectedSource = (Function) sourceFunctionsCBModel.getSelectedItem(); + reloadTargetList(selectedSource); + loadFunctions(selectedSource, (Function) targetFunctionsCBModel.getSelectedItem()); + + ((FunctionComparisonProvider) provider).setTabText(this); + ((FunctionComparisonProvider) provider) + .setTitle(((FunctionComparisonProvider) provider).getTabText()); + + // Fire a notification to update the UI state; without this the + // actions would not be properly enabled/disabled + tool.contextChanged(provider); + tool.setStatusInfo("function comparisons updated"); + }); + } + + /** + * Returns the combo box (source or target) which has focus + * + * @return the focused component + */ + public JComboBox getFocusedComponent() { + CodeComparisonPanel currentComponent = + getCurrentComponent(); + boolean sourceHasFocus = currentComponent.leftPanelHasFocus(); + return sourceHasFocus ? sourceFunctionsCB : targetFunctionsCB; + } + + /** + * Clears out and reloads the source function list. Any selection currently + * made on the list will be reestablished. + */ + private void reloadSourceList() { + + // Save off any selected item so we can restore if it later + Function selection = (Function) sourceFunctionsCBModel.getSelectedItem(); + + // Remove all functions + sourceFunctionsCBModel.removeAllElements(); + + // Reload the functions + FunctionComparisonModel model = ((FunctionComparisonProvider) provider).getModel(); + Iterator compIter = model.getComparisons().iterator(); + while (compIter.hasNext()) { + FunctionComparison fc = compIter.next(); + sourceFunctionsCBModel.addElement(fc.getSource()); + } + + restoreSelection(sourceFunctionsCB, selection); + } + + /** + * Clears out and reloads the target function list with functions + * associated with the given source function. Any selection currently made + * on the list will be reestablished. + * + * @param source the selected source function + */ + private void reloadTargetList(Function source) { + + // Save off any selected item so we can restore if it later + Function selection = (Function) targetFunctionsCBModel.getSelectedItem(); + + // Remove all functions + targetFunctionsCBModel.removeAllElements(); + + // Find all target functions associated with the given source function + // and add them to the combo box model + FunctionComparisonModel model = ((FunctionComparisonProvider) provider).getModel(); + Iterator compIter = model.getComparisons().iterator(); + while (compIter.hasNext()) { + FunctionComparison fc = compIter.next(); + if (fc.getSource().equals(source)) { + Set targets = fc.getTargets(); + targetFunctionsCBModel.addAll(targets); + } + } + + restoreSelection(targetFunctionsCB, selection); + } + + /** + * Sets a given function to be the selected item in a given combo + * box. If the function isn't found, the first item in the box is + * set. + * + * @param cb the combo box + * @param selection the function to set + */ + private void restoreSelection(JComboBox cb, Function selection) { + ComboBoxModel model = cb.getModel(); + + boolean found = false; + for (int i = 0; i < model.getSize(); i++) { + Function f = model.getElementAt(i); + if (f.equals(selection)) { + model.setSelectedItem(f); + found = true; + break; + } + } + + if (!found && model.getSize() > 0) { + cb.setSelectedIndex(0); + } + } + + /** + * Creates the panel displaying the source combo box + *

    + * Note: The custom renderer is used so the name of the program associated + * with each function can be displayed in the combo box; this is necessary + * since a combo box may show functions from any number of programs, and + * the default is to simply show the function name
    + * eg: "init (notepad)"
    + * + * @return the source panel + */ + private JPanel createSourcePanel() { + JPanel panel = new JPanel(new BorderLayout()); + sourceFunctionsCB = new JComboBox<>(); + sourceFunctionsCBModel = new DefaultComboBoxModel<>(); + sourceFunctionsCB.setModel(sourceFunctionsCBModel); + sourceFunctionsCB.setRenderer(new FunctionListCellRenderer()); + sourceFunctionsCB.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + if (e.getStateChange() != ItemEvent.SELECTED) { + return; + } + + Function selected = (Function) sourceFunctionsCBModel.getSelectedItem(); + loadFunctions(selected, null); + + // Each time a source function is selected we need + // to load the targets associated with it + reloadTargetList((Function) sourceFunctionsCBModel.getSelectedItem()); + + // Fire a notification to update the UI state; without this the + // actions would not be properly enabled/disabled + tool.contextChanged(provider); + } + }); + + panel.add(sourceFunctionsCB, BorderLayout.CENTER); + return panel; + } + + /** + * Creates the panel for the target functions selection components + *

    + * Note: The custom renderer is used so the name of the program associated + * with each function can be displayed in the combo box; this is necessary + * since a combo box may show functions from any number of programs, and + * the default is to simply show the function name
    + * eg: "init (notepad)"
    + * + * @return the target panel + */ + private JPanel createTargetPanel() { + JPanel panel = new JPanel(new BorderLayout()); + targetFunctionsCB = new JComboBox<>(); + targetFunctionsCBModel = new DefaultComboBoxModel<>(); + targetFunctionsCB.setModel(targetFunctionsCBModel); + targetFunctionsCB.setRenderer(new FunctionListCellRenderer()); + targetFunctionsCB.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + if (e.getStateChange() != ItemEvent.SELECTED) { + return; + } + + Function selected = (Function) targetFunctionsCBModel.getSelectedItem(); + loadFunctions((Function) sourceFunctionsCBModel.getSelectedItem(), selected); + + // Fire a notification to update the UI state; without this the + // actions would not be properly enabled/disabled + tool.contextChanged(provider); + } + }); + + panel.add(targetFunctionsCB, BorderLayout.CENTER); + return panel; + } + + /** + * Cell renderer for combo boxes that changes the default display to show + * both the function name and the program it comes from + */ + private class FunctionListCellRenderer extends DefaultListCellRenderer { + + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, + boolean isSelected, boolean cellHasFocus) { + + if (value == null) { + // It's possible during a close program operation to have this + // renderer called with a null value. If so, we can't get the + // function so just use the default renderer. + return super.getListCellRendererComponent(list, value, index, isSelected, + cellHasFocus); + } + + Function f = (Function) value; + String text = f.getName() + " (" + f.getProgram().getName() + ")"; + return super.getListCellRendererComponent(list, text, index, isSelected, + cellHasFocus); + } } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/MultiFunctionComparisonProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/MultiFunctionComparisonProvider.java new file mode 100644 index 0000000000..0d57424345 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/MultiFunctionComparisonProvider.java @@ -0,0 +1,76 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.functioncompare; + +import docking.action.DockingAction; +import ghidra.app.plugin.core.functioncompare.actions.*; +import ghidra.framework.plugintool.Plugin; + +/** + * Provider for a {@link MultiFunctionComparisonPanel}. This differs from the + * base comparison provider in that it has additional actions that are + * appropriate for managing multiple comparisons (add, remove, etc...). + */ +public class MultiFunctionComparisonProvider extends FunctionComparisonProvider { + + /** + * Constructor + * + * @param plugin the parent plugin + */ + public MultiFunctionComparisonProvider(Plugin plugin) { + super(plugin, "functioncomparisonprovider", plugin.getName()); + } + + @Override + public FunctionComparisonPanel getComponent() { + if (functionComparisonPanel == null) { + functionComparisonPanel = new MultiFunctionComparisonPanel(this, tool); + } + return functionComparisonPanel; + } + + @Override + boolean isEmpty() { + return model.getSourceFunctions().isEmpty(); + } + + @Override + protected void initFunctionComparisonPanel() { + super.initFunctionComparisonPanel(); + + DockingAction nextFunctionAction = new NextFunctionAction(this); + DockingAction previousFunctionAction = new PreviousFunctionAction(this); + DockingAction removeFunctionsAction = new RemoveFunctionsAction(this); + DockingAction openFunctionTableAction = getOpenFunctionTableAction(); + + addLocalAction(nextFunctionAction); + addLocalAction(previousFunctionAction); + addLocalAction(removeFunctionsAction); + addLocalAction(openFunctionTableAction); + } + + /** + * Returns an action that opens a table from which users may select + * functions for comparison. By default this returns an action that will + * open a standard function table, but may be overridden as-needed. + * + * @return the docking action + */ + protected DockingAction getOpenFunctionTableAction() { + return new OpenFunctionTableAction(tool, this); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/AbstractApplyFunctionSignatureAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/AbstractApplyFunctionSignatureAction.java similarity index 83% rename from Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/AbstractApplyFunctionSignatureAction.java rename to Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/AbstractApplyFunctionSignatureAction.java index 0617ac54d1..9464f12602 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/AbstractApplyFunctionSignatureAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/AbstractApplyFunctionSignatureAction.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ghidra.app.plugin.core.functioncompare; +package ghidra.app.plugin.core.functioncompare.actions; import docking.ActionContext; import docking.ComponentProvider; @@ -30,10 +30,11 @@ import ghidra.util.exception.DuplicateNameException; import ghidra.util.exception.InvalidInputException; /** - * Action that applies the signature of the function in the currently active side of a - * code comparison panel to the function in the other side of the panel. - *
    Each CodeComparisonPanel can extend this class in order to provide this action - * using its context. + * Applies the signature of the function in the currently active side of a + * code comparison panel to the function in the other side of the panel + *

    + * Each CodeComparisonPanel can extend this class in order to provide this action + * using its context */ public abstract class AbstractApplyFunctionSignatureAction extends DockingAction { @@ -42,9 +43,9 @@ public abstract class AbstractApplyFunctionSignatureAction extends DockingAction private static final String ACTION_NAME = "Apply Function Signature To Other Side"; /** - * Constructor for the action that applies a function signature from one side of a code - * comparison panel to the other. - * @param owner the owner of this action. + * Constructor + * + * @param owner the owner of this action */ public AbstractApplyFunctionSignatureAction(String owner) { super(ACTION_NAME, owner); @@ -97,13 +98,25 @@ public abstract class AbstractApplyFunctionSignatureAction extends DockingAction } } + /** + * Returns true if the comparison panel opposite the one with focus, + * is read-only + *

    + * eg: if the right-side panel has focus, and the left-side panel is + * read-only, this will return true + * + * @param codeComparisonPanel the comparison panel + * @return true if the non-focused panel is read-only + */ protected boolean hasReadOnlyNonFocusedSide( CodeComparisonPanel codeComparisonPanel) { Function leftFunction = codeComparisonPanel.getLeftFunction(); Function rightFunction = codeComparisonPanel.getRightFunction(); + if (leftFunction == null || rightFunction == null) { return false; // Doesn't have a function on both sides. } + boolean leftHasFocus = codeComparisonPanel.leftPanelHasFocus(); Program leftProgram = leftFunction.getProgram(); Program rightProgram = rightFunction.getProgram(); @@ -111,11 +124,22 @@ public abstract class AbstractApplyFunctionSignatureAction extends DockingAction (leftHasFocus && rightProgram.getDomainFile().isReadOnly()); } + /** + * Attempts to change the signature of a function to that of another + * function + * + * @param provider the parent component provider + * @param destinationFunction the function to change + * @param sourceFunction the function to copy + * @return true if the operation was successful + */ protected boolean updateFunction(ComponentProvider provider, Function destinationFunction, Function sourceFunction) { + Program program = destinationFunction.getProgram(); int txID = program.startTransaction(ACTION_NAME); boolean commit = false; + try { FunctionUtility.updateFunction(destinationFunction, sourceFunction); commit = true; @@ -129,6 +153,7 @@ public abstract class AbstractApplyFunctionSignatureAction extends DockingAction finally { program.endTransaction(txID, commit); } + return commit; } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/CompareFunctionsAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/CompareFunctionsAction.java new file mode 100644 index 0000000000..dbb811930a --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/CompareFunctionsAction.java @@ -0,0 +1,105 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.functioncompare.actions; + +import java.util.Set; + +import javax.swing.Icon; +import javax.swing.ImageIcon; + +import docking.ActionContext; +import docking.action.*; +import ghidra.app.services.FunctionComparisonService; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.listing.Function; +import ghidra.util.HelpLocation; +import resources.MultiIcon; +import resources.ResourceManager; +import resources.icons.ScaledImageIconWrapper; +import resources.icons.TranslateIcon; + +/** + * Creates a new comparison between a set of functions, launching a new + * comparison provider in the process + *

    + * This class is abstract to force implementors to supply the source of the + * functions (may be the listing, a table, etc...) + * + * @see {@link #getSelectedFunctions(ActionContext) getSelectedFunctions} + */ +public abstract class CompareFunctionsAction extends DockingAction { + + protected FunctionComparisonService comparisonService; + + private static final ImageIcon COMPARISON_ICON = + ResourceManager.loadImage("images/page_white_c.png"); + private static final Icon NEW_ICON = ResourceManager.loadImage("images/bullet_star.png"); + private static final Icon SCALED_NEW_ICON = new ScaledImageIconWrapper(NEW_ICON, 16, 16); + private static final Icon TRANSLATED_NEW_ICON = new TranslateIcon(SCALED_NEW_ICON, 4, -4); + private static final Icon CREATE_NEW_COMPARISON_ICON = + new MultiIcon(COMPARISON_ICON, TRANSLATED_NEW_ICON); + private static final String CREATE_COMPARISON_GROUP = "A9_CreateComparison"; + + /** + * Constructor + * + * @param tool the plugin tool + */ + public CompareFunctionsAction(PluginTool tool) { + super("Compare Functions", tool.getName()); + this.comparisonService = tool.getService(FunctionComparisonService.class); + setActionAttributes(); + } + + @Override + public void actionPerformed(ActionContext context) { + Set functions = getSelectedFunctions(context); + comparisonService.compareFunctions(functions); + } + + @Override + public boolean isEnabledForContext(ActionContext actionContext) { + Set functions = getSelectedFunctions(actionContext); + return !functions.isEmpty(); + } + + /** + * Returns the icon to use for the action + * + * @return the icon + */ + protected Icon getToolBarIcon() { + return CREATE_NEW_COMPARISON_ICON; + } + + /** + * Returns the set of functions that will be sent to the comparison service + * + * @param actionContext the current action context + * @return set of functions to be compared + */ + protected abstract Set getSelectedFunctions(ActionContext actionContext); + + private void setActionAttributes() { + setDescription("Create Function Comparison"); + setPopupMenuData(new MenuData(new String[] { "Compare Selected Functions" }, + getToolBarIcon(), CREATE_COMPARISON_GROUP)); + ToolBarData newToolBarData = + new ToolBarData(getToolBarIcon(), CREATE_COMPARISON_GROUP); + setToolBarData(newToolBarData); + setHelpLocation(new HelpLocation("FunctionComparison", "Function_Comparison")); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/CompareFunctionsFromFunctionTableAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/CompareFunctionsFromFunctionTableAction.java new file mode 100644 index 0000000000..42b2a4535e --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/CompareFunctionsFromFunctionTableAction.java @@ -0,0 +1,99 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.functioncompare.actions; + +import java.util.*; + +import docking.ActionContext; +import ghidra.app.plugin.core.functionwindow.FunctionRowObject; +import ghidra.app.plugin.core.functionwindow.FunctionTableModel; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.listing.*; +import ghidra.util.table.GhidraTable; + +/** + * Creates a comparison between a set of functions extracted from selections in + * a ghidra table. By default this table is assumed to be constructed using a + * {@link FunctionTableModel}. If the {@link ActionContext context} for + * this action does NOT meet those parameters this action will not even be + * enabled. + *

    + * If this action is to be used with a different type of table, simply + * extend this class and override {@link #getSelectedFunctions(ActionContext) getSelectedFunctions} + * and {@link #isModelSupported(ActionContext) isModelSupported} as-needed. + */ +public class CompareFunctionsFromFunctionTableAction extends CompareFunctionsAction { + + /** + * Constructor + * + * @param tool the plugin tool + */ + public CompareFunctionsFromFunctionTableAction(PluginTool tool) { + super(tool); + } + + @Override + public boolean isAddToPopup(ActionContext context) { + return isModelSupported(context); + } + + @Override + public boolean isValidContext(ActionContext context) { + return isModelSupported(context); + } + + @Override + protected Set getSelectedFunctions(ActionContext actionContext) { + Set functions = new HashSet<>(); + + GhidraTable table = (GhidraTable) actionContext.getContextObject(); + int[] selectedRows = table.getSelectedRows(); + if (selectedRows.length == 0) { + return Collections.emptySet(); + } + FunctionTableModel model = (FunctionTableModel) table.getModel(); + Program program = model.getProgram(); + FunctionManager functionManager = program.getFunctionManager(); + List functionRowObjects = model.getRowObjects(selectedRows); + for (FunctionRowObject functionRowObject : functionRowObjects) { + long key = functionRowObject.getKey(); + Function rowFunction = functionManager.getFunction(key); + functions.add(rowFunction); + } + return functions; + } + + /** + * Helper method to determine if the current context is one that this + * action supports (eg: is this action being applied to a table that + * contains function information?). + *

    + * By default this method verifies that the table in question is a + * {@link FunctionTableModel}. If another table is being used, override this + * method. + * + * @param context the action context + * @return true if the context is a function table model + */ + protected boolean isModelSupported(ActionContext context) { + if (!(context.getContextObject() instanceof GhidraTable)) { + return false; + } + GhidraTable table = (GhidraTable) context.getContextObject(); + return table.getModel() instanceof FunctionTableModel; + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/CompareFunctionsFromListingAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/CompareFunctionsFromListingAction.java new file mode 100644 index 0000000000..f8e8272509 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/CompareFunctionsFromListingAction.java @@ -0,0 +1,65 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.functioncompare.actions; + +import java.util.HashSet; +import java.util.Set; + +import docking.ActionContext; +import ghidra.app.context.ListingActionContext; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.listing.*; +import ghidra.program.util.ProgramSelection; + +/** + * Creates a comparison between a set of functions extracted from selections + * in the listing + */ +public class CompareFunctionsFromListingAction extends CompareFunctionsAction { + + /** + * Constructor + * + * @param tool the plugin tool + */ + public CompareFunctionsFromListingAction(PluginTool tool) { + super(tool); + } + + @Override + public boolean isAddToPopup(ActionContext actionContext) { + return actionContext instanceof ListingActionContext; + } + + @Override + public boolean isValidContext(ActionContext context) { + return context instanceof ListingActionContext; + } + + @Override + protected Set getSelectedFunctions(ActionContext actionContext) { + ListingActionContext listingContext = (ListingActionContext) actionContext; + ProgramSelection selection = listingContext.getSelection(); + Program program = listingContext.getProgram(); + FunctionManager functionManager = program.getFunctionManager(); + Set functions = new HashSet<>(); + FunctionIterator functionIter = functionManager.getFunctions(selection, true); + for (Function selectedFunction : functionIter) { + functions.add(selectedFunction); + } + return functions; + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/NextFunctionAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/NextFunctionAction.java new file mode 100644 index 0000000000..522829c0bb --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/NextFunctionAction.java @@ -0,0 +1,98 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.functioncompare.actions; + +import java.awt.Component; +import java.awt.event.InputEvent; + +import javax.swing.Icon; +import javax.swing.JComboBox; + +import docking.ActionContext; +import docking.ComponentProvider; +import docking.action.*; +import ghidra.app.plugin.core.functioncompare.MultiFunctionComparisonPanel; +import ghidra.app.plugin.core.functioncompare.MultiFunctionComparisonProvider; +import ghidra.program.model.listing.Function; +import ghidra.util.HelpLocation; +import resources.MultiIcon; +import resources.ResourceManager; +import resources.icons.TranslateIcon; + +/** + * Displays the next available function in the function comparison panel. If + * already at the end of the list, the action will not be enabled. + */ +public class NextFunctionAction extends DockingAction { + + private static final String FUNCTION_NAVIGATE_GROUP = "A9_FunctionNavigate"; + private static final Icon NEXT_ICON = + new TranslateIcon(ResourceManager.loadImage("images/arrow_down.png"), 3, 1); + private static final Icon FUNCTION_ICON = + new TranslateIcon(ResourceManager.loadImage("images/FunctionScope.gif"), -5, -2); + private static final Icon NEXT_FUNCTION_ICON = new MultiIcon(NEXT_ICON, FUNCTION_ICON); + + /** + * Constructor + * + * @param provider the comparison provider for this action + */ + public NextFunctionAction(MultiFunctionComparisonProvider provider) { + super("Compare Next Function", provider.getOwner()); + + setKeyBindingData( + new KeyBindingData('N', InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK)); + setDescription("Compare the next function for the side with focus."); + setPopupMenuData( + new MenuData(new String[] { "Compare The Next Function" }, NEXT_FUNCTION_ICON, + FUNCTION_NAVIGATE_GROUP)); + + ToolBarData newToolBarData = + new ToolBarData(NEXT_FUNCTION_ICON, FUNCTION_NAVIGATE_GROUP); + setToolBarData(newToolBarData); + + HelpLocation helpLocation = + new HelpLocation(MultiFunctionComparisonPanel.HELP_TOPIC, "Navigate_Next"); + setHelpLocation(helpLocation); + } + + @Override + public boolean isEnabledForContext(ActionContext context) { + if (!(context.getComponentProvider() instanceof MultiFunctionComparisonProvider)) { + return false; + } + MultiFunctionComparisonProvider provider = + (MultiFunctionComparisonProvider) context.getComponentProvider(); + + Component comp = provider.getComponent(); + if (!(comp instanceof MultiFunctionComparisonPanel)) { + return false; + } + + MultiFunctionComparisonPanel panel = (MultiFunctionComparisonPanel) comp; + JComboBox focusedComponent = panel.getFocusedComponent(); + return focusedComponent.getSelectedIndex() < (focusedComponent.getModel().getSize() - 1); + } + + @Override + public void actionPerformed(ActionContext context) { + ComponentProvider provider = context.getComponentProvider(); + MultiFunctionComparisonPanel panel = (MultiFunctionComparisonPanel) provider.getComponent(); + JComboBox focusedComponent = panel.getFocusedComponent(); + focusedComponent.setSelectedIndex(focusedComponent.getSelectedIndex() + 1); + provider.contextChanged(); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/OpenFunctionTableAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/OpenFunctionTableAction.java new file mode 100644 index 0000000000..f9af770766 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/OpenFunctionTableAction.java @@ -0,0 +1,121 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.functioncompare.actions; + +import java.util.*; +import java.util.stream.Collectors; + +import javax.swing.Icon; +import javax.swing.ImageIcon; + +import docking.ActionContext; +import docking.action.*; +import docking.widgets.dialogs.TableChooserDialog; +import ghidra.app.plugin.core.functioncompare.FunctionComparisonProvider; +import ghidra.app.plugin.core.functioncompare.MultiFunctionComparisonPanel; +import ghidra.app.plugin.core.functionwindow.FunctionRowObject; +import ghidra.app.plugin.core.functionwindow.FunctionTableModel; +import ghidra.app.services.FunctionComparisonService; +import ghidra.app.services.ProgramManager; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.Program; +import ghidra.util.HelpLocation; +import ghidra.util.SystemUtilities; +import resources.MultiIcon; +import resources.ResourceManager; +import resources.icons.ScaledImageIconWrapper; +import resources.icons.TranslateIcon; +import util.CollectionUtils; + +/** + * Opens a table chooser allowing the user to select functions from the current + * program. The table displayed uses a {@link FunctionTableModel}. + * + * @see FunctionComparisonService + */ +public class OpenFunctionTableAction extends DockingAction { + + private static final Icon ADD_ICON = ResourceManager.loadImage("images/Plus.png"); + private static final Icon SCALED_ADD_ICON = new ScaledImageIconWrapper(ADD_ICON, 10, 10); + private static final ImageIcon COMPARISON_ICON = + ResourceManager.loadImage("images/page_white_c.png"); + private static final Icon TRANSLATED_ADD_ICON = new TranslateIcon(SCALED_ADD_ICON, 8, 1); + private static final String ADD_COMPARISON_GROUP = "A9_AddToComparison"; + private static final Icon ADD_TO_COMPARISON_ICON = + new MultiIcon(COMPARISON_ICON, TRANSLATED_ADD_ICON); + + protected PluginTool tool; + protected ProgramManager programManagerService; + protected FunctionComparisonService comparisonService; + + /** + * Constructor + * + * @param tool the plugin tool + * @param provider the function comparison provider + */ + public OpenFunctionTableAction(PluginTool tool, FunctionComparisonProvider provider) { + super("Add Functions To Comparison", provider.getOwner()); + + this.tool = tool; + this.programManagerService = tool.getService(ProgramManager.class); + this.comparisonService = tool.getService(FunctionComparisonService.class); + + setDescription("Add functions to comparison"); + setPopupMenuData(new MenuData(new String[] { "Add functions" }, + ADD_TO_COMPARISON_ICON, ADD_COMPARISON_GROUP)); + + ToolBarData newToolBarData = + new ToolBarData(ADD_TO_COMPARISON_ICON, ADD_COMPARISON_GROUP); + setToolBarData(newToolBarData); + + HelpLocation helpLocation = new HelpLocation(MultiFunctionComparisonPanel.HELP_TOPIC, + "Add_To_Comparison"); + setHelpLocation(helpLocation); + } + + @Override + public boolean isEnabledForContext(ActionContext context) { + return context.getComponentProvider() instanceof FunctionComparisonProvider; + } + + @Override + public void actionPerformed(ActionContext context) { + Runnable runnable = () -> { + FunctionComparisonProvider provider = + (FunctionComparisonProvider) context.getComponentProvider(); + Program currentProgram = programManagerService.getCurrentProgram(); + FunctionTableModel model = new FunctionTableModel(tool, currentProgram); + model.reload(programManagerService.getCurrentProgram()); + + TableChooserDialog diag = + new TableChooserDialog<>("Select Functions: " + currentProgram.getName(), + model, true); + tool.showDialog(diag); + List rows = diag.getSelectionItems(); + if (CollectionUtils.isBlank(rows)) { + return; // the table chooser can return null if the operation was cancelled + } + + Set functions = + rows.stream().map(row -> row.getFunction()).collect(Collectors.toSet()); + comparisonService.compareFunctions(new HashSet<>(functions), provider); + }; + + SystemUtilities.runSwingLater(runnable); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/PreviousFunctionAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/PreviousFunctionAction.java new file mode 100644 index 0000000000..87ae7e8579 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/PreviousFunctionAction.java @@ -0,0 +1,98 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.functioncompare.actions; + +import java.awt.Component; +import java.awt.event.InputEvent; + +import javax.swing.Icon; +import javax.swing.JComboBox; + +import docking.ActionContext; +import docking.ComponentProvider; +import docking.action.*; +import ghidra.app.plugin.core.functioncompare.MultiFunctionComparisonPanel; +import ghidra.app.plugin.core.functioncompare.MultiFunctionComparisonProvider; +import ghidra.program.model.listing.Function; +import ghidra.util.HelpLocation; +import resources.MultiIcon; +import resources.ResourceManager; +import resources.icons.TranslateIcon; + +/** + * Displays the previous function in the function comparison panel. If + * already at the beginning of the list, the action will not be enabled. + */ +public class PreviousFunctionAction extends DockingAction { + + private static final String FUNCTION_NAVIGATE_GROUP = "A9_FunctionNavigate"; + private static final Icon PREVIOUS_ICON = + new TranslateIcon(ResourceManager.loadImage("images/arrow_up.png"), 3, 1); + private static final Icon FUNCTION_ICON = + new TranslateIcon(ResourceManager.loadImage("images/FunctionScope.gif"), -5, -2); + private static final Icon PREVIOUS_FUNCTION_ICON = new MultiIcon(PREVIOUS_ICON, FUNCTION_ICON); + + /** + * Constructor + * + * @param provider the function comparison provider + */ + public PreviousFunctionAction(MultiFunctionComparisonProvider provider) { + super("Compare Previous Function", provider.getOwner()); + + setKeyBindingData( + new KeyBindingData('P', InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK)); + setDescription("Compare the previous function for the side with focus."); + setPopupMenuData(new MenuData(new String[] { "Compare The Previous Function" }, + PREVIOUS_FUNCTION_ICON, FUNCTION_NAVIGATE_GROUP)); + + ToolBarData newToolBarData = + new ToolBarData(PREVIOUS_FUNCTION_ICON, FUNCTION_NAVIGATE_GROUP); + setToolBarData(newToolBarData); + + HelpLocation helpLocation = + new HelpLocation(MultiFunctionComparisonPanel.HELP_TOPIC, + "Navigate Previous"); + setHelpLocation(helpLocation); + } + + @Override + public boolean isEnabledForContext(ActionContext context) { + if (!(context.getComponentProvider() instanceof MultiFunctionComparisonProvider)) { + return false; + } + MultiFunctionComparisonProvider provider = + (MultiFunctionComparisonProvider) context.getComponentProvider(); + + Component comp = provider.getComponent(); + if (!(comp instanceof MultiFunctionComparisonPanel)) { + return false; + } + + MultiFunctionComparisonPanel panel = (MultiFunctionComparisonPanel) comp; + JComboBox focusedComponent = panel.getFocusedComponent(); + return focusedComponent.getSelectedIndex() > 0; + } + + @Override + public void actionPerformed(ActionContext context) { + ComponentProvider provider = context.getComponentProvider(); + MultiFunctionComparisonPanel panel = (MultiFunctionComparisonPanel) provider.getComponent(); + JComboBox focusedComponent = panel.getFocusedComponent(); + focusedComponent.setSelectedIndex(focusedComponent.getSelectedIndex() - 1); + provider.contextChanged(); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/RemoveFunctionsAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/RemoveFunctionsAction.java new file mode 100644 index 0000000000..f1cead3ba8 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/RemoveFunctionsAction.java @@ -0,0 +1,100 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.functioncompare.actions; + +import java.awt.Component; +import java.awt.event.InputEvent; +import java.util.Arrays; +import java.util.HashSet; + +import javax.swing.Icon; +import javax.swing.JComboBox; + +import docking.ActionContext; +import docking.action.*; +import ghidra.app.plugin.core.functioncompare.MultiFunctionComparisonPanel; +import ghidra.app.plugin.core.functioncompare.MultiFunctionComparisonProvider; +import ghidra.program.model.listing.Function; +import ghidra.util.HelpLocation; +import resources.MultiIcon; +import resources.ResourceManager; +import resources.icons.TranslateIcon; + +/** + * Removes the currently-selected function from the comparison panel. If no + * functions are enabled, the action will be disabled. + */ +public class RemoveFunctionsAction extends DockingAction { + + private static final Icon FUNCTION_ICON = + new TranslateIcon(ResourceManager.loadImage("images/FunctionScope.gif"), -5, -2); + private static final Icon REMOVE_ICON = + new TranslateIcon(ResourceManager.loadImage("images/edit-delete.png"), 3, 3); + private static final String REMOVE_FUNCTION_GROUP = "A9_RemoveFunctions"; + private static final Icon REMOVE_FUNCTION_ICON = new MultiIcon(REMOVE_ICON, FUNCTION_ICON); + + /** + * Constructor + * + * @param provider the function comparison provider + */ + public RemoveFunctionsAction(MultiFunctionComparisonProvider provider) { + super("Remove Functions", provider.getOwner()); + + setKeyBindingData( + new KeyBindingData('R', InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK)); + setDescription("Removes function in the focused comparison panel"); + setPopupMenuData(new MenuData(new String[] { "Remove Function" }, + REMOVE_FUNCTION_ICON, REMOVE_FUNCTION_GROUP)); + + ToolBarData newToolBarData = + new ToolBarData(REMOVE_FUNCTION_ICON, REMOVE_FUNCTION_GROUP); + setToolBarData(newToolBarData); + + HelpLocation helpLocation = + new HelpLocation(MultiFunctionComparisonPanel.HELP_TOPIC, "Remove_From_Comparison"); + setHelpLocation(helpLocation); + } + + @Override + public boolean isEnabledForContext(ActionContext context) { + if (!(context.getComponentProvider() instanceof MultiFunctionComparisonProvider)) { + return false; + } + MultiFunctionComparisonProvider provider = + (MultiFunctionComparisonProvider) context.getComponentProvider(); + + Component comp = provider.getComponent(); + if (!(comp instanceof MultiFunctionComparisonPanel)) { + return false; + } + MultiFunctionComparisonPanel panel = (MultiFunctionComparisonPanel) comp; + JComboBox focusedComponent = panel.getFocusedComponent(); + + return focusedComponent.getSelectedIndex() != -1; + } + + @Override + public void actionPerformed(ActionContext context) { + MultiFunctionComparisonProvider provider = + (MultiFunctionComparisonProvider) context.getComponentProvider(); + JComboBox focusedComponent = + ((MultiFunctionComparisonPanel) provider.getComponent()).getFocusedComponent(); + Function selectedFunction = (Function) focusedComponent.getSelectedItem(); + provider.removeFunctions(new HashSet<>(Arrays.asList(selectedFunction))); + provider.contextChanged(); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionRowObject.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionRowObject.java index 0f0b6ae1e4..7a364035d0 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionRowObject.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionRowObject.java @@ -17,15 +17,15 @@ package ghidra.app.plugin.core.functionwindow; import ghidra.program.model.listing.Function; -class FunctionRowObject implements Comparable { +public class FunctionRowObject implements Comparable { private final Function function; - FunctionRowObject(Function function) { + public FunctionRowObject(Function function) { this.function = function; } - Function getFunction() { + public Function getFunction() { return function; } @@ -54,7 +54,7 @@ class FunctionRowObject implements Comparable { return true; } - long getKey() { + public long getKey() { return function.getID(); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionTableModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionTableModel.java index 43b87a3c40..a4f10a06a4 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionTableModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionTableModel.java @@ -29,7 +29,7 @@ import ghidra.util.table.AddressBasedTableModel; import ghidra.util.table.field.*; import ghidra.util.task.TaskMonitor; -class FunctionTableModel extends AddressBasedTableModel { +public class FunctionTableModel extends AddressBasedTableModel { static final int LOCATION_COL_WIDTH = 50; @@ -39,7 +39,7 @@ class FunctionTableModel extends AddressBasedTableModel { private FunctionManager functionMgr; - FunctionTableModel(PluginTool tool, Program program) { + public FunctionTableModel(PluginTool tool, Program program) { super("Functions", tool, program, null); } @@ -66,7 +66,7 @@ class FunctionTableModel extends AddressBasedTableModel { return descriptor; } - void reload(Program newProgram) { + public void reload(Program newProgram) { this.setProgram(newProgram); if (newProgram != null) { functionMgr = newProgram.getFunctionManager(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionWindowPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionWindowPlugin.java index 8ed11b2128..e79b8eb92e 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionWindowPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionWindowPlugin.java @@ -15,33 +15,34 @@ */ package ghidra.app.plugin.core.functionwindow; -import java.util.List; +import javax.swing.KeyStroke; -import javax.swing.ImageIcon; - -import docking.ActionContext; -import docking.action.*; +import docking.ComponentProvider; +import docking.ComponentProviderActivationListener; +import docking.action.DockingAction; +import docking.action.KeyBindingData; import ghidra.app.CorePluginPackage; import ghidra.app.events.ProgramClosedPluginEvent; -import ghidra.app.events.ProgramSelectionPluginEvent; import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.plugin.ProgramPlugin; import ghidra.app.plugin.core.functioncompare.FunctionComparisonProvider; -import ghidra.app.plugin.core.functioncompare.FunctionComparisonProviderManager; +import ghidra.app.plugin.core.functioncompare.actions.CompareFunctionsFromFunctionTableAction; +import ghidra.app.services.FunctionComparisonService; import ghidra.framework.model.*; +import ghidra.framework.options.OptionsChangeListener; +import ghidra.framework.options.ToolOptions; import ghidra.framework.plugintool.PluginInfo; import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.util.PluginStatus; import ghidra.program.model.address.Address; -import ghidra.program.model.listing.*; +import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.Program; import ghidra.program.model.symbol.Symbol; -import ghidra.program.util.*; -import ghidra.util.Msg; -import ghidra.util.table.GhidraTable; +import ghidra.program.util.ChangeManager; +import ghidra.program.util.ProgramChangeRecord; import ghidra.util.table.SelectionNavigationAction; import ghidra.util.table.actions.MakeProgramSelectionAction; import ghidra.util.task.SwingUpdateManager; -import resources.ResourceManager; //@formatter:off @PluginInfo( @@ -50,24 +51,28 @@ import resources.ResourceManager; category = PluginCategoryNames.CODE_VIEWER, shortDescription = "Function Viewer", description = "Provides a window that displays the list of functions in the program.", + servicesRequired = { FunctionComparisonService.class }, eventsConsumed = { ProgramClosedPluginEvent.class } ) //@formatter:on -public class FunctionWindowPlugin extends ProgramPlugin implements DomainObjectListener { +public class FunctionWindowPlugin extends ProgramPlugin implements DomainObjectListener, + OptionsChangeListener, ComponentProviderActivationListener { private DockingAction selectAction; - private DockingAction compareAction; + private DockingAction compareFunctionsAction; private FunctionWindowProvider provider; private SwingUpdateManager swingMgr; - private FunctionComparisonProviderManager functionComparisonManager; + private FunctionComparisonService functionComparisonService; public FunctionWindowPlugin(PluginTool tool) { super(tool, true, false); - functionComparisonManager = new FunctionComparisonProviderManager(this); - - swingMgr = new SwingUpdateManager(1000, () -> provider.reload()); - + swingMgr = new SwingUpdateManager(1000, new Runnable() { + @Override + public void run() { + provider.reload(); + } + }); } @Override @@ -75,7 +80,21 @@ public class FunctionWindowPlugin extends ProgramPlugin implements DomainObjectL super.init(); provider = new FunctionWindowProvider(this); + functionComparisonService = tool.getService(FunctionComparisonService.class); createActions(); + + /** + * Kicks the tool actions to set the proper enablement when selection changes + * on the function table + */ + provider.getTable().getSelectionModel().addListSelectionListener(x -> { + tool.contextChanged(provider); + }); + + // Listen for providers being opened/closed to we can disable the + // add-to-comparison action if there are no comparison windows + // open. + functionComparisonService.addFunctionComparisonProviderListener(this); } @Override @@ -84,15 +103,14 @@ public class FunctionWindowPlugin extends ProgramPlugin implements DomainObjectL currentProgram.removeListener(this); } swingMgr.dispose(); - provider.dispose(); + if (provider != null) { + provider.dispose(); + } super.dispose(); } @Override public void domainObjectChanged(DomainObjectChangedEvent ev) { - if (ev.containsEvent(DomainObject.DO_OBJECT_RESTORED)) { - functionComparisonManager.domainObjectRestored(ev); - } if (!provider.isVisible()) { return; @@ -181,82 +199,64 @@ public class FunctionWindowPlugin extends ProgramPlugin implements DomainObjectL } private void createActions() { - addSelectAction(); - addCompareAction(); - DockingAction action = new SelectionNavigationAction(this, provider.getTable()); tool.addLocalAction(provider, action); - } - - private void addSelectAction() { selectAction = new MakeProgramSelectionAction(this, provider.getTable()); tool.addLocalAction(provider, selectAction); + + compareFunctionsAction = new CompareFunctionsFromFunctionTableAction(tool); + tool.addLocalAction(provider, compareFunctionsAction); } - private void addCompareAction() { - compareAction = new DockingAction("Compare Selected Functions", getName()) { - @Override - public void actionPerformed(ActionContext context) { - compareSelectedFunctions(); - } - }; +// private void installDummyAction(DockingAction action) { +// DummyKeyBindingsOptionsAction dummyAction = +// new DummyKeyBindingsOptionsAction(action.getName(), null); +// tool.addAction(dummyAction); +// +// ToolOptions options = tool.getOptions(DockingToolConstants.KEY_BINDINGS); +// options.addOptionsChangeListener(this); +// +// KeyStroke keyStroke = options.getKeyStroke(dummyAction.getFullName(), null); +// if (keyStroke != null) { +// action.setUnvalidatedKeyBindingData(new KeyBindingData(keyStroke)); +// } +// } - ImageIcon icon = ResourceManager.loadImage("images/page_white_c.png"); - compareAction.setPopupMenuData(new MenuData(new String[] { "Compare Functions" }, icon)); - compareAction.setDescription("Compares the currently selected function(s) in the table."); - compareAction.setToolBarData(new ToolBarData(icon)); + @Override + public void optionsChanged(ToolOptions options, String optionName, Object oldValue, + Object newValue) { - tool.addLocalAction(provider, compareAction); + if (optionName.startsWith(selectAction.getName())) { + KeyStroke keyStroke = (KeyStroke) newValue; + selectAction.setUnvalidatedKeyBindingData(new KeyBindingData(keyStroke)); + } + if (optionName.startsWith(compareFunctionsAction.getName())) { + KeyStroke keyStroke = (KeyStroke) newValue; + compareFunctionsAction.setUnvalidatedKeyBindingData(new KeyBindingData(keyStroke)); + } } void setActionsEnabled(boolean enabled) { selectAction.setEnabled(enabled); - compareAction.setEnabled(enabled); + compareFunctionsAction.setEnabled(enabled); } void showFunctions() { provider.showFunctions(); } - private void selectFunctions(ProgramSelection selection) { - ProgramSelectionPluginEvent pspe = - new ProgramSelectionPluginEvent("Selection", selection, currentProgram); - firePluginEvent(pspe); - } - - private FunctionComparisonProvider compareSelectedFunctions() { - Function[] functions = getSelectedFunctions(); - if (functions.length < 2) { - Msg.showError(this, provider.getComponent(), "Compare Selected Functions", - "Select two or more rows in the table indicating functions to compare."); - return null; + @Override + public void componentProviderActivated(ComponentProvider componentProvider) { + if (componentProvider instanceof FunctionComparisonProvider) { + tool.contextChanged(provider); } - return functionComparisonManager.showFunctionComparisonProvider(functions); - } - - /** - * Gets the functions that are currently selected in the table. - * @return the selected functions - */ - private Function[] getSelectedFunctions() { - GhidraTable table = provider.getTable(); - int[] selectedRows = table.getSelectedRows(); - Function[] functions = new Function[selectedRows.length]; - FunctionTableModel model = provider.getModel(); - Program program = model.getProgram(); - FunctionManager functionManager = program.getFunctionManager(); - List functionRowObjects = model.getRowObjects(selectedRows); - int index = 0; - for (FunctionRowObject functionRowObject : functionRowObjects) { - long key = functionRowObject.getKey(); - functions[index++] = functionManager.getFunction(key); - } - return functions; } @Override - protected void programClosed(Program program) { - functionComparisonManager.closeProviders(program); + public void componentProviderDeactivated(ComponentProvider componentProvider) { + if (componentProvider instanceof FunctionComparisonProvider) { + tool.contextChanged(provider); + } } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionWindowProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionWindowProvider.java index bf174778d2..80445e9b1c 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionWindowProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionWindowProvider.java @@ -33,7 +33,7 @@ import ghidra.util.table.*; import resources.ResourceManager; /** - * Provider for the equates table. + * Provider that displays all functions in the selected program */ public class FunctionWindowProvider extends ComponentProviderAdapter { @@ -45,9 +45,13 @@ public class FunctionWindowProvider extends ComponentProviderAdapter { private JComponent mainPanel; private GhidraTableFilterPanel tableFilterPanel; - private GhidraThreadedTablePanel threadedTablePanel; + /** + * Constructor + * + * @param plugin the function window plugin + */ FunctionWindowProvider(FunctionWindowPlugin plugin) { super(plugin.getTool(), "Functions Window", plugin.getName()); setTitle("Functions"); @@ -124,8 +128,9 @@ public class FunctionWindowProvider extends ComponentProviderAdapter { functionTable.setPreferredScrollableViewportSize(new Dimension(350, 150)); functionTable.setRowSelectionAllowed(true); functionTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); - functionTable.getSelectionModel().addListSelectionListener( - e -> plugin.setActionsEnabled(functionTable.getSelectedRowCount() > 0)); + functionTable.getSelectionModel() + .addListSelectionListener( + e -> plugin.setActionsEnabled(functionTable.getSelectedRowCount() > 0)); functionModel.addTableModelListener(e -> { int rowCount = functionModel.getRowCount(); @@ -158,8 +163,10 @@ public class FunctionWindowProvider extends ComponentProviderAdapter { } private void setFunctionTableRenderer() { - functionTable.getColumnModel().getColumn(FunctionTableModel.LOCATION_COL).setPreferredWidth( - FunctionTableModel.LOCATION_COL_WIDTH); + functionTable.getColumnModel() + .getColumn(FunctionTableModel.LOCATION_COL) + .setPreferredWidth( + FunctionTableModel.LOCATION_COL_WIDTH); } void update(Function function) { @@ -203,5 +210,4 @@ public class FunctionWindowProvider extends ComponentProviderAdapter { public boolean isTransient() { return false; } - } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/services/FunctionComparisonModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/services/FunctionComparisonModel.java new file mode 100644 index 0000000000..e3f542bb07 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/services/FunctionComparisonModel.java @@ -0,0 +1,355 @@ +/* ### + * 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.services; + +import java.util.*; +import java.util.stream.Collectors; + +import org.apache.commons.collections4.CollectionUtils; + +import ghidra.app.plugin.core.functioncompare.*; +import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.Program; +import ghidra.util.Msg; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskLauncher; + +/** + * A collection of {@link FunctionComparison function comparison} + * objects that describe how functions may be compared. Each comparison object + * is a mapping of a function (source) to a list of functions (targets). + *

    + * This model is intended to be used by the {@link FunctionComparisonProvider} + * as the basis for its display. It should never be created manually, and should + * only be accessed via the {@link FunctionComparisonService}. + *

    + * Note: Subscribers may register to be informed of changes to this model via the + * {@link FunctionComparisonModelListener comparison model listener} interface. + */ +public class FunctionComparisonModel { + + private List comparisons = new ArrayList<>(); + private List listeners = new ArrayList<>(); + + /** + * Adds the given subscriber to the list of those to be notified of model + * changes + * + * @param listener the model change subscriber + */ + public void addFunctionComparisonModelListener(FunctionComparisonModelListener listener) { + listeners.add(listener); + } + + /** + * Returns a list of all comparisons in the model, in sorted order by + * source function name + * + * @return a list of all comparisons in the model + */ + public List getComparisons() { + List toReturn = new ArrayList<>(); + toReturn.addAll(comparisons); + Collections.sort(toReturn); + return toReturn; + } + + /** + * Replaces the current model with the comparisons provided + * + * @param comparisons the new comparison model + */ + public void setComparisons(List comparisons) { + this.comparisons = comparisons; + } + + /** + * Adds a single comparison to the model + * + * @param comparison the comparison to add + */ + public void addComparison(FunctionComparison comparison) { + comparisons.add(comparison); + } + + /** + * Returns a list of all targets in the model (across all comparisons) for + * a given source function + * + * @param source the source function + * @return list of associated target functions + */ + public Set getTargets(Function source) { + Set targets = new HashSet<>(); + for (FunctionComparison fc : comparisons) { + if (fc.getSource().equals(source)) { + targets.addAll(fc.getTargets()); + } + } + + return targets; + } + + /** + * Updates the model with a set of functions to compare. This will add the + * functions to any existing {@link FunctionComparison comparisons} in the + * model and create new comparisons for functions not represented. + *

    + * Note: It is assumed that when using this method, all functions can be + * compared with all other functions; meaning each function will be added as + * both a source AND a target. To specify a specific source/target + * relationship, see {@link #compareFunctions(Function, Function)}. + * + * @param functions the set of functions to compare + */ + public void compareFunctions(Set functions) { + if (CollectionUtils.isEmpty(functions)) { + return; // not an error, just return + } + + addToExistingComparisons(functions); + createNewComparisons(functions); + + fireModelChanged(); + } + + /** + * Compares two functions. If a comparison already exists in the model for + * the given source, the target will simply be added to it; otherwise a + * new comparison will be created. + * + * @param source the source function + * @param target the target function + */ + public void compareFunctions(Function source, Function target) { + FunctionComparison fc = getOrCreateComparison(source); + fc.addTarget(target); + + fireModelChanged(); + } + + /** + * Removes the given function from all comparisons in the model, whether + * stored as a source or target + * + * @param function the function to remove + */ + public void removeFunction(Function function) { + List comparisonsToRemove = new ArrayList<>(); + + Iterator iter = comparisons.iterator(); + while (iter.hasNext()) { + + // First remove any comparisons that have the function as its + // source + FunctionComparison fc = iter.next(); + if (fc.getSource().equals(function)) { + comparisonsToRemove.add(fc); + continue; + } + + // Now remove the function from the target list (if it's there) + fc.getTargets().remove(function); + } + + comparisons.removeAll(comparisonsToRemove); + + fireModelChanged(); + } + + /** + * Removes all functions in the model that come from the given + * program + * + * @param program the program to remove functions from + */ + public void removeFunctions(Program program) { + Set sources = getSourceFunctions(); + Set targets = getTargetFunctions(); + + Set sourcesToRemove = sources.stream() + .filter(f -> f.getProgram().equals(program)) + .collect(Collectors.toSet()); + + Set targetsToRemove = targets.stream() + .filter(f -> f.getProgram().equals(program)) + .collect(Collectors.toSet()); + + sourcesToRemove.stream().forEach(f -> removeFunction(f)); + targetsToRemove.stream().forEach(f -> removeFunction(f)); + } + + /** + * Returns all source functions in the model + * + * @return a set of all source functions + */ + public Set getSourceFunctions() { + Set items = new HashSet<>(); + for (FunctionComparison fc : comparisons) { + items.add(fc.getSource()); + } + return items; + } + + /** + * Returns all target functions in the model + * + * @return a set of all target functions + */ + public Set getTargetFunctions() { + Set items = new HashSet<>(); + Iterator iter = comparisons.iterator(); + while (iter.hasNext()) { + FunctionComparison fc = iter.next(); + items.addAll(fc.getTargets()); + } + + return items; + } + + /** + * Returns a set of all target functions for a given source + * + * @param source the source function to search for + * @return the set of associated target functions + */ + public Set getTargetFunctions(Function source) { + Set items = new HashSet<>(); + Iterator iter = comparisons.iterator(); + while (iter.hasNext()) { + FunctionComparison fc = iter.next(); + if (!fc.getSource().equals(source)) { + continue; + } + items.addAll(fc.getTargets()); + } + + return items; + } + + /** + * Creates a {@link FunctionComparison comparison} for each function + * given, such that each comparison will have every other function as its + * targets. For example, given three functions, f1, f2, and f3, this is what the + * model will look like after this call: + *

  • comparison 1:
  • + *
      + *
    • source: f1
    • + *
    • targets: f2, f3
    • + *
    + *
  • comparison 2:
  • + *
      + *
    • source: f2
    • + *
    • targets: f1, f3
    • + *
    + *
  • comparison 3:
  • + *
      + *
    • source: f3
    • + *
    • targets: f1, f2
    • + *
    + * + * If this model already contains a comparison for a given function + * (meaning the model contains a comparison with the function as the + * source) then that function is skipped. + *

    + * Note that this could be a long-running process if many (thousands) + * functions are chosen to compare, hence the monitored task. In practice + * this should never be the case, as users will likely not be + * comparing more than a handful of functions at any given time. + * + * @param functions the set of functions to create comparisons for + */ + private void createNewComparisons(Set functions) { + + TaskLauncher.launchModal("Creating Comparisons", (monitor) -> { + + // Remove any functions that already have an comparison in the + // model; these will be ignored + functions.removeIf(f -> comparisons.stream() + .anyMatch(fc -> f.equals(fc.getSource()))); + + monitor.setIndeterminate(false); + monitor.setMessage("Creating new comparisons"); + monitor.initialize(functions.size()); + + // Save off all the existing targets in the model; these have to be + // added to any new comparisons + Set existingTargets = getTargetFunctions(); + + // Now loop over the given functions and create new comparisons + for (Function f : functions) { + try { + monitor.checkCanceled(); + } + catch (CancelledException e) { + Msg.info(this, "Function comparison operation cancelled"); + return; + } + FunctionComparison fc = new FunctionComparison(); + fc.setSource(f); + fc.addTargets(functions); + fc.addTargets(existingTargets); + comparisons.add(fc); + monitor.incrementProgress(1); + } + }); + + } + + /** + * Searches the model for a comparison that has the given function as its + * source; if not found, a new comparison is created + * + * @param source the source function to search for + * @return a function comparison object for the given source + */ + private FunctionComparison getOrCreateComparison(Function source) { + for (FunctionComparison fc : comparisons) { + if (fc.getSource().equals(source)) { + return fc; + } + } + + FunctionComparison fc = new FunctionComparison(); + fc.setSource(source); + comparisons.add(fc); + return fc; + } + + /** + * Adds a given set of functions to every target list in every + * comparison in the model + * + * @param functions the functions to add + */ + private void addToExistingComparisons(Set functions) { + for (FunctionComparison fc : comparisons) { + fc.getTargets().addAll(functions); + } + } + + /** + * Sends model-change notifications to all subscribers. The updated model + * is sent in the callback. + */ + private void fireModelChanged() { + listeners.forEach(l -> l.modelChanged(comparisons)); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/services/FunctionComparisonService.java b/Ghidra/Features/Base/src/main/java/ghidra/app/services/FunctionComparisonService.java new file mode 100644 index 0000000000..38b2eae7ee --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/services/FunctionComparisonService.java @@ -0,0 +1,97 @@ +/* ### + * 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.services; + +import java.util.Set; + +import docking.ComponentProviderActivationListener; +import ghidra.app.plugin.core.functioncompare.FunctionComparisonPlugin; +import ghidra.app.plugin.core.functioncompare.FunctionComparisonProvider; +import ghidra.framework.plugintool.ServiceInfo; +import ghidra.program.model.listing.Function; + +/** + * Allows users to create comparisons between functions which will be displayed + * side-by-side in a {@link FunctionComparisonProvider} + */ +@ServiceInfo(defaultProvider = FunctionComparisonPlugin.class) +public interface FunctionComparisonService { + + /** + * Creates a comparison between a set of functions, where each function + * in the list can be compared against any other function in the list + * + * @param functions the functions to compare + * @return the new comparison provider + */ + public FunctionComparisonProvider compareFunctions(Set functions); + + /** + * Creates a comparison between two functions + * + * @param source a function in the comparison + * @param target a function in the comparison + * @return the comparison provider + */ + public FunctionComparisonProvider compareFunctions(Function source, + Function target); + + /** + * Creates a comparison between a set of functions, adding them to the + * given comparison provider + * + * @param functions the functions to compare + * @param provider the provider to add the comparisons to + */ + public void compareFunctions(Set functions, + FunctionComparisonProvider provider); + + /** + * Creates a comparison between two functions and adds it to a given + * comparison provider + * + * @param source a function in the comparison + * @param target a function in the comparison + * @param provider the provider to add the comparison to + */ + public void compareFunctions(Function source, Function target, + FunctionComparisonProvider provider); + + /** + * Removes a given function from all comparisons across all comparison + * providers + * + * @param function the function to remove + */ + public void removeFunction(Function function); + + /** + * Removes a given function from all comparisons in the given comparison + * provider + * + * @param function the function to remove + * @param provider the comparison provider to remove functions from + */ + public void removeFunction(Function function, FunctionComparisonProvider provider); + + /** + * Adds the given listener to the list of subscribers who wish to be + * notified of provider activation events (eg: provider open/close) + * + * @param listener the listener to be added + */ + public void addFunctionComparisonProviderListener(ComponentProviderActivationListener listener); +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ApplyFunctionSignatureAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ApplyFunctionSignatureAction.java index b60497c6b6..b8fb6d529f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ApplyFunctionSignatureAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ApplyFunctionSignatureAction.java @@ -17,7 +17,7 @@ package ghidra.app.util.viewer.listingpanel; import docking.ActionContext; import docking.widgets.fieldpanel.internal.FieldPanelCoordinator; -import ghidra.app.plugin.core.functioncompare.AbstractApplyFunctionSignatureAction; +import ghidra.app.plugin.core.functioncompare.actions.AbstractApplyFunctionSignatureAction; import ghidra.app.util.viewer.util.CodeComparisonPanel; /** diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingCodeComparisonPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingCodeComparisonPanel.java index b165f5b519..631e608a6c 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingCodeComparisonPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingCodeComparisonPanel.java @@ -361,15 +361,23 @@ public class ListingCodeComparisonPanel } private void updateLeftListingTitle() { + titlePanels[LEFT].setTitleName(getLeftProgramName()); + } + + private String getLeftProgramName() { String leftProgramName = (programs[LEFT] != null) ? programs[LEFT].getDomainFile().toString() : "none"; - titlePanels[LEFT].setTitleName(leftProgramName); + return leftProgramName; } private void updateRightListingTitle() { + titlePanels[RIGHT].setTitleName(getRightProgramName()); + } + + private String getRightProgramName() { String rightProgramName = (programs[RIGHT] != null) ? programs[RIGHT].getDomainFile().toString() : "none"; - titlePanels[RIGHT].setTitleName(rightProgramName); + return rightProgramName; } private void initializeListingFieldNavigation() { @@ -466,18 +474,10 @@ public class ListingCodeComparisonPanel public void updateActionEnablement() { boolean isShowing = isShowing(); boolean listingDiffActionEnablement = isShowing && listingDiff.hasCorrelation(); - toggleHoverAction.setEnabled(isShowing); - nextPreviousAreaMarkerAction.setEnabled(listingDiffActionEnablement); - nextDiffAction.setEnabled(listingDiffActionEnablement); - previousDiffAction.setEnabled(listingDiffActionEnablement); - optionsAction.setEnabled(listingDiffActionEnablement); - // Diff actions + tool.contextChanged(tool.getActiveComponentProvider()); + diffActionManager.updateActionEnablement(listingDiffActionEnablement); - - // applyFunctionSignature enablement is handled by context. - - // For now don't do anything here with the header or orientation actions. } class ToggleHeaderAction extends ToggleDockingAction { @@ -537,6 +537,11 @@ public class ListingCodeComparisonPanel setHover(true); } + @Override + public boolean isEnabledForContext(ActionContext context) { + return isShowing(); + } + @Override public void actionPerformed(ActionContext context) { setHover(isSelected()); @@ -684,6 +689,11 @@ public class ListingCodeComparisonPanel previousDiffAction.setMenuString(); } + @Override + public boolean isEnabledForContext(ActionContext context) { + return isShowing() && listingDiff.hasCorrelation(); + } + @Override public void actionStateChanged(ActionState newActionState, EventTrigger trigger) { adjustNextPreviousAreaType(); @@ -718,6 +728,11 @@ public class ListingCodeComparisonPanel return isValidPanelContext(context); } + @Override + public boolean isEnabledForContext(ActionContext context) { + return isShowing() && listingDiff.hasCorrelation(); + } + @Override public void actionPerformed(ActionContext context) { if (isValidContext(context)) { @@ -757,6 +772,11 @@ public class ListingCodeComparisonPanel return isValidPanelContext(context); } + @Override + public boolean isEnabledForContext(ActionContext context) { + return isShowing() && listingDiff.hasCorrelation(); + } + @Override public void actionPerformed(ActionContext context) { if (isValidContext(context)) { @@ -784,6 +804,11 @@ public class ListingCodeComparisonPanel setEnabled(true); } + @Override + public boolean isEnabledForContext(ActionContext context) { + return isShowing() && listingDiff.hasCorrelation(); + } + @Override public boolean isValidContext(ActionContext context) { return isValidPanelContext(context); @@ -1518,8 +1543,9 @@ public class ListingCodeComparisonPanel comparisonOptions.getUnmatchedCodeUnitsBackgroundColor(); if (programs[LEFT] != null) { AddressIndexMap indexMap = listingPanels[LEFT].getAddressIndexMap(); - listingPanels[LEFT].getFieldPanel().setBackgroundColorModel( - new MarkerServiceBackgroundColorModel(markerManagers[LEFT], indexMap)); + listingPanels[LEFT].getFieldPanel() + .setBackgroundColorModel( + new MarkerServiceBackgroundColorModel(markerManagers[LEFT], indexMap)); markerManagers[LEFT].setProgram(programs[LEFT]); unmatchedCodeMarkers[LEFT] = markerManagers[LEFT].createAreaMarker("Listing1 Unmatched Code", @@ -1532,8 +1558,10 @@ public class ListingCodeComparisonPanel } if (programs[RIGHT] != null) { AddressIndexMap rightIndexMap = listingPanels[RIGHT].getAddressIndexMap(); - listingPanels[RIGHT].getFieldPanel().setBackgroundColorModel( - new MarkerServiceBackgroundColorModel(markerManagers[RIGHT], rightIndexMap)); + listingPanels[RIGHT].getFieldPanel() + .setBackgroundColorModel( + new MarkerServiceBackgroundColorModel(markerManagers[RIGHT], + rightIndexMap)); markerManagers[RIGHT].setProgram(programs[RIGHT]); unmatchedCodeMarkers[RIGHT] = markerManagers[RIGHT].createAreaMarker("Listing2 Unmatched Code", @@ -1633,8 +1661,9 @@ public class ListingCodeComparisonPanel indexMaps[LEFT] = new AddressIndexMap(addressSets[LEFT]); markerManagers[LEFT].getOverviewProvider().setAddressIndexMap(indexMaps[LEFT]); - listingPanels[LEFT].getFieldPanel().setBackgroundColorModel( - new MarkerServiceBackgroundColorModel(markerManagers[LEFT], indexMaps[LEFT])); + listingPanels[LEFT].getFieldPanel() + .setBackgroundColorModel( + new MarkerServiceBackgroundColorModel(markerManagers[LEFT], indexMaps[LEFT])); } private void updateRightAddressSet(Function rightFunction) { @@ -1649,8 +1678,9 @@ public class ListingCodeComparisonPanel indexMaps[RIGHT] = new AddressIndexMap(addressSets[RIGHT]); markerManagers[RIGHT].getOverviewProvider().setAddressIndexMap(indexMaps[RIGHT]); - listingPanels[RIGHT].getFieldPanel().setBackgroundColorModel( - new MarkerServiceBackgroundColorModel(markerManagers[RIGHT], indexMaps[RIGHT])); + listingPanels[RIGHT].getFieldPanel() + .setBackgroundColorModel( + new MarkerServiceBackgroundColorModel(markerManagers[RIGHT], indexMaps[RIGHT])); } @Override @@ -1992,6 +2022,11 @@ public class ListingCodeComparisonPanel setDualPanelFocus(i); } } + + // Kick the tool so action buttons will be updated + if (tool.getActiveComponentProvider() != null) { + tool.getActiveComponentProvider().contextChanged(); + } } private void setDualPanelFocus(int leftOrRight) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingComparisonFieldPanelCoordinator.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingComparisonFieldPanelCoordinator.java index 5a801a3414..d116a75103 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingComparisonFieldPanelCoordinator.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingComparisonFieldPanelCoordinator.java @@ -127,10 +127,12 @@ public class ListingComparisonFieldPanelCoordinator extends LayoutLockedFieldPan ListingPanel rightListingPanel = dualListingPanel.getRightPanel(); AddressIndexMap leftAddressIndexMap = leftListingPanel.getAddressIndexMap(); AddressIndexMap rightAddressIndexMap = rightListingPanel.getAddressIndexMap(); + BigInteger leftIndex = (leftAddress != null) ? leftAddressIndexMap.getIndex(leftAddress) : null; BigInteger rightIndex = (rightAddress != null) ? rightAddressIndexMap.getIndex(rightAddress) : null; + BigInteger[] lineNumbers = new BigInteger[] { (leftIndex != null) ? leftIndex : BigInteger.ZERO, (rightIndex != null) ? rightIndex : BigInteger.ZERO }; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/CodeComparisonPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/CodeComparisonPanel.java index 317895a4ee..673a010c6c 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/CodeComparisonPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/CodeComparisonPanel.java @@ -31,15 +31,17 @@ import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.address.AddressSet; import ghidra.program.model.address.AddressSetView; import ghidra.program.model.listing.*; +import ghidra.util.classfinder.ClassSearcher; import ghidra.util.classfinder.ExtensionPoint; /** - * The CodeComparisonPanel class should be extended by any class that is to be discovered by - * the {@link FunctionComparisonPanel} class and included as a form of comparing two sections - * of code within the same or different programs. - *

    - * NOTE: ALL CodeComparisonPanel CLASSES MUST END IN "CodeComparisonPanel". - * If not, the ClassSearcher will not find them. + * The CodeComparisonPanel class should be extended by any class that is to be + * discovered by the {@link FunctionComparisonPanel} class and included as a + * form of comparing two sections of code within the same or different programs + *

    + * NOTE: ALL CodeComparisonPanel CLASSES MUST END IN < + * code>CodeComparisonPanel so they are discoverable by the + * {@link ClassSearcher} */ public abstract class CodeComparisonPanel extends JPanel implements ExtensionPoint, FocusListener { @@ -68,15 +70,18 @@ public abstract class CodeComparisonPanel exten protected Function[] functions = new Function[2]; protected Data[] data = new Data[2]; + /** If true, the title of each comparison panel will be shown */ + private boolean showTitles = true; + private boolean syncScrolling = false; private T fieldPanelCoordinator; /** - * Base constructor - * @param owner the name of the owner of this component (typically the name of the plugin that - * owns this panel.) - * @param tool the tool that contains the component. + * Constructor + * + * @param owner the name of the owner of this component + * @param tool the tool that contains the component */ protected CodeComparisonPanel(String owner, PluginTool tool) { this.owner = owner; @@ -84,34 +89,38 @@ public abstract class CodeComparisonPanel exten } /** - * The GUI component for this CodeComparisonPanel. A CodeComparisonPanel provides a - * dual display with a left and right side for comparing some part of the code for two programs. - * @return the component. + * The GUI component for this CodeComparisonPanel + * + * @return the component */ public abstract JComponent getComponent(); /** - * The title for this code comparison component. - * @return the title. + * The title for this code comparison panel + * + * @return the title */ public abstract String getTitle(); /** - * Specifies the two programs to be compared by this panel. - * @param leftProgram the program for the left side. - * @param rightProgram the program for the right side. + * Specifies the two programs to be compared by this panel + * + * @param leftProgram the program for the left side + * @param rightProgram the program for the right side */ protected abstract void setPrograms(Program leftProgram, Program rightProgram); /** - * Displays a comparison of two program's functions. + * Displays a comparison of two program's functions + * * @param leftFunction the function to show in the left side of the code comparison view * @param rightFunction the function to show in the right side of the code comparison view */ public abstract void loadFunctions(Function leftFunction, Function rightFunction); /** - * Displays a comparison of two program's data items. + * Displays a comparison of two program's data items + * * @param leftData the data item to show in the left side of the code comparison view * @param rightData the data item to show in the right side of the code comparison view */ @@ -119,7 +128,8 @@ public abstract class CodeComparisonPanel exten /** * Displays program information for a particular set of addresses in the two programs - * being compared. + * being compared + * * @param leftProgram the program in the left side of the code comparison view * @param rightProgram the program in the right side of the code comparison view * @param leftAddresses the addresses of the program info to show in the left side @@ -129,19 +139,21 @@ public abstract class CodeComparisonPanel exten AddressSetView leftAddresses, AddressSetView rightAddresses); /** - * Cleans up resources when this panel is no longer needed. + * Cleans up resources when this panel is no longer needed */ public abstract void dispose(); /** - * Enable/disable navigation in this panel using the mouse. - * @param enabled false disables mouse navigation. + * Enable/disable navigation in this panel using the mouse + * + * @param enabled false disables mouse navigation */ public abstract void setMouseNavigationEnabled(boolean enabled); /** - * Get the actions for this CodeComparisonPanel. - * @return an array containing the actions + * Returns the actions for this panel + * + * @return an array of docking actions */ public DockingAction[] getActions() { // No actions currently that appear for each CodeComparisonPanel. @@ -151,6 +163,14 @@ public abstract class CodeComparisonPanel exten return actions; } + public boolean getShowTitles() { + return showTitles; + } + + public void setShowTitles(boolean showTitles) { + this.showTitles = showTitles; + } + /** * Determines if this panel is intended to take the place of another and if so it returns * the class of the panel to be superseded. diff --git a/Ghidra/Features/Base/src/main/resources/images/bullet_star.png b/Ghidra/Features/Base/src/main/resources/images/bullet_star.png new file mode 100644 index 0000000000000000000000000000000000000000..fab774a3288143c3ca5ef4a6354476a816d20b29 GIT binary patch literal 331 zcmV-R0kr;!P)clpQjpWPb8b##8}RLd@5ygx>`#(pz>k}$oIkF|*aK~E`Efn%|Bp+N z|GyqlYyW&e-v0A$8BQV$NSgWcM%Moyw~GJ&deHs<=iR3N-_Hg9|8m&q|L5&8GYK1T zJ%$-*`^F!)N`MCR01asV|LsD^f1vFfKW>v|CMpboe%Jke!RPThe API methods being tested: {@link FunctionComparisonService} + *

  • The model being used for verification: {@link FunctionComparison}
  • + */ +public class CompareFunctionsTest extends AbstractGhidraHeadedIntegrationTest { + + private Program program1; + private Program program2; + private Function foo; + private Function bar; + private Function junk; + private Function stuff; + private Function one; + private Function two; + private Function three; + private Function four; + private Function five; + private FunctionComparisonPlugin plugin; + private FunctionComparisonProvider provider; + private FunctionComparisonProvider provider0; + private FunctionComparisonModel model; + + @Before + public void setUp() throws Exception { + DummyPluginTool tool = new DummyPluginTool(); + plugin = new FunctionComparisonPlugin(tool); + assertNotNull(plugin); + buildTestProgram1(); + buildTestProgram2(); + + model = createTestModel(); + } + + /** + * + * Tests for {@link FunctionComparisonService#compareFunctions(Set)} + * + */ + + @Test + public void testSetNoFunctions() throws Exception { + Set functions = CompareFunctionsTestUtility.getFunctionsAsSet(); + FunctionComparisonProvider provider = plugin.compareFunctions(functions); + assert (provider == null); + } + + @Test + public void testSetOneFunction() throws Exception { + Set functions = CompareFunctionsTestUtility.getFunctionsAsSet(foo); + provider = plugin.compareFunctions(functions); + CompareFunctionsTestUtility.checkSourceFunctions(provider, foo); + CompareFunctionsTestUtility.checkTargetFunctions(provider, foo, foo); + } + + @Test + public void testSetDuplicateFunctionDifferentProviders() throws Exception { + Set functions = CompareFunctionsTestUtility.getFunctionsAsSet(foo); + provider = plugin.compareFunctions(functions); + CompareFunctionsTestUtility.checkSourceFunctions(provider, foo); + CompareFunctionsTestUtility.checkTargetFunctions(provider, foo, foo); + + provider0 = plugin.compareFunctions(functions); + CompareFunctionsTestUtility.checkSourceFunctions(provider0, foo); + CompareFunctionsTestUtility.checkTargetFunctions(provider0, foo, foo); + } + + @Test + public void testSetDuplicateFunctionSameProvider() throws Exception { + Set functions = CompareFunctionsTestUtility.getFunctionsAsSet(foo); + provider = plugin.compareFunctions(functions); + CompareFunctionsTestUtility.checkSourceFunctions(provider, foo); + CompareFunctionsTestUtility.checkTargetFunctions(provider, foo, foo); + + plugin.compareFunctions(functions, provider); + CompareFunctionsTestUtility.checkSourceFunctions(provider, foo); + CompareFunctionsTestUtility.checkTargetFunctions(provider, foo, foo); + } + + @Test + public void testSetMultipleFunctions() throws Exception { + Set functions = CompareFunctionsTestUtility.getFunctionsAsSet(foo, junk, stuff); + provider = plugin.compareFunctions(functions); + CompareFunctionsTestUtility.checkSourceFunctions(provider, foo, junk, stuff); + CompareFunctionsTestUtility.checkTargetFunctions(provider, foo, foo, junk, stuff); + CompareFunctionsTestUtility.checkTargetFunctions(provider, junk, foo, junk, stuff); + CompareFunctionsTestUtility.checkTargetFunctions(provider, stuff, foo, junk, stuff); + } + + @Test + public void testSetMultipleFunctionsMultipleSets() throws Exception { + Set functions1 = CompareFunctionsTestUtility.getFunctionsAsSet(one, two); + Set functions2 = CompareFunctionsTestUtility.getFunctionsAsSet(three, four, five); + + provider = plugin.compareFunctions(functions1); + provider0 = plugin.compareFunctions(functions2); + + CompareFunctionsTestUtility.checkSourceFunctions(provider, one, two); + CompareFunctionsTestUtility.checkTargetFunctions(provider, one, one, two); + CompareFunctionsTestUtility.checkTargetFunctions(provider, two, one, two); + CompareFunctionsTestUtility.checkSourceFunctions(provider0, three, four, five); + CompareFunctionsTestUtility.checkTargetFunctions(provider0, three, three, four, five); + CompareFunctionsTestUtility.checkTargetFunctions(provider0, four, three, four, five); + CompareFunctionsTestUtility.checkTargetFunctions(provider0, five, three, four, five); + } + + @Test + public void testSetCombineTwoSets() throws Exception { + Set functions1 = CompareFunctionsTestUtility.getFunctionsAsSet(foo, two); + Set functions2 = CompareFunctionsTestUtility.getFunctionsAsSet(bar, three, four); + + provider = plugin.compareFunctions(functions1); + plugin.compareFunctions(functions2, provider); + + CompareFunctionsTestUtility.checkSourceFunctions(provider, foo, two, bar, three, four); + CompareFunctionsTestUtility.checkTargetFunctions(provider, foo, foo, two, bar, three, four); + CompareFunctionsTestUtility.checkTargetFunctions(provider, two, foo, two, bar, three, four); + CompareFunctionsTestUtility.checkTargetFunctions(provider, bar, foo, two, bar, three, four); + CompareFunctionsTestUtility.checkTargetFunctions(provider, three, foo, two, bar, three, + four); + CompareFunctionsTestUtility.checkTargetFunctions(provider, four, foo, two, bar, three, + four); + } + + /** + * + * Tests for {@link FunctionComparisonService#compareFunctions(Set, FunctionComparisonProvider)} + * + */ + + @Test + public void testSetAddToSpecificProvider() throws Exception { + Set functions1 = CompareFunctionsTestUtility.getFunctionsAsSet(foo, two); + Set functions2 = CompareFunctionsTestUtility.getFunctionsAsSet(bar, three); + Set functions3 = CompareFunctionsTestUtility.getFunctionsAsSet(four); + provider = plugin.compareFunctions(functions1); + provider0 = plugin.compareFunctions(functions2); + + plugin.compareFunctions(functions3, provider0); + + CompareFunctionsTestUtility.checkSourceFunctions(provider, foo, two); + CompareFunctionsTestUtility.checkSourceFunctions(provider0, bar, three, four); + CompareFunctionsTestUtility.checkTargetFunctions(provider, foo, foo, two); + CompareFunctionsTestUtility.checkTargetFunctions(provider, two, foo, two); + CompareFunctionsTestUtility.checkTargetFunctions(provider0, bar, bar, three, four); + CompareFunctionsTestUtility.checkTargetFunctions(provider0, three, bar, three, four); + CompareFunctionsTestUtility.checkTargetFunctions(provider0, four, bar, three, four); + } + + /** + * + * Tests for {@link FunctionComparisonService#removeFunction(Function)} + * + */ + + @Test + public void testRemoveFunction() throws Exception { + Set functions = CompareFunctionsTestUtility.getFunctionsAsSet(foo, bar); + provider = plugin.compareFunctions(functions); + + CompareFunctionsTestUtility.checkSourceFunctions(provider, foo, bar); + CompareFunctionsTestUtility.checkTargetFunctions(provider, foo, foo, bar); + CompareFunctionsTestUtility.checkTargetFunctions(provider, bar, foo, bar); + + plugin.removeFunction(foo); + + CompareFunctionsTestUtility.checkSourceFunctions(provider, bar); + CompareFunctionsTestUtility.checkTargetFunctions(provider, bar, bar); + } + + @Test + public void testRemoveFunctionTargetOnly() throws Exception { + Set functions = CompareFunctionsTestUtility.getFunctionsAsSet(foo, bar); + provider = plugin.compareFunctions(functions); + plugin.compareFunctions(foo, two, provider); // add a target to foo, which is not also a source + + // Verify the structure with the new target + CompareFunctionsTestUtility.checkSourceFunctions(provider, foo, bar); + CompareFunctionsTestUtility.checkTargetFunctions(provider, foo, foo, bar, two); + CompareFunctionsTestUtility.checkTargetFunctions(provider, bar, foo, bar); + + plugin.removeFunction(two); + + // Verify the new target is gone + CompareFunctionsTestUtility.checkSourceFunctions(provider, foo, bar); + CompareFunctionsTestUtility.checkTargetFunctions(provider, foo, foo, bar); + CompareFunctionsTestUtility.checkTargetFunctions(provider, bar, foo, bar); + } + + @Test + public void testRemoveFunctionMultipleProviders() throws Exception { + Set functions = CompareFunctionsTestUtility.getFunctionsAsSet(foo, bar); + provider = plugin.compareFunctions(functions); + provider0 = plugin.compareFunctions(functions); + + CompareFunctionsTestUtility.checkSourceFunctions(provider, foo, bar); + CompareFunctionsTestUtility.checkSourceFunctions(provider0, foo, bar); + + plugin.removeFunction(foo); + + CompareFunctionsTestUtility.checkSourceFunctions(provider, bar); + CompareFunctionsTestUtility.checkSourceFunctions(provider0, bar); + } + + @Test + public void testRemoveNonexistentFunction() throws Exception { + Set functions = CompareFunctionsTestUtility.getFunctionsAsSet(foo, bar); + provider = plugin.compareFunctions(functions); + + CompareFunctionsTestUtility.checkSourceFunctions(provider, foo, bar); + CompareFunctionsTestUtility.checkTargetFunctions(provider, foo, foo, bar); + CompareFunctionsTestUtility.checkTargetFunctions(provider, bar, foo, bar); + + plugin.removeFunction(two); // nothing should happen + + CompareFunctionsTestUtility.checkSourceFunctions(provider, foo, bar); + CompareFunctionsTestUtility.checkTargetFunctions(provider, foo, foo, bar); + CompareFunctionsTestUtility.checkTargetFunctions(provider, bar, foo, bar); + } + + /** + * + * Tests for {@link FunctionComparisonService#removeFunction(Function, FunctionComparisonProvider)} + * + */ + + @Test + public void testRemoveFunctionFromSpecificProvider() throws Exception { + Set functions1 = CompareFunctionsTestUtility.getFunctionsAsSet(foo, bar); + provider = plugin.compareFunctions(functions1); + provider0 = plugin.compareFunctions(functions1); + + CompareFunctionsTestUtility.checkSourceFunctions(provider, foo, bar); + CompareFunctionsTestUtility.checkTargetFunctions(provider, foo, foo, bar); + CompareFunctionsTestUtility.checkTargetFunctions(provider, bar, foo, bar); + CompareFunctionsTestUtility.checkSourceFunctions(provider0, foo, bar); + CompareFunctionsTestUtility.checkTargetFunctions(provider0, foo, foo, bar); + CompareFunctionsTestUtility.checkTargetFunctions(provider0, bar, foo, bar); + + plugin.removeFunction(foo, provider); + + CompareFunctionsTestUtility.checkSourceFunctions(provider, bar); + CompareFunctionsTestUtility.checkTargetFunctions(provider, bar, bar); + CompareFunctionsTestUtility.checkSourceFunctions(provider0, foo, bar); + CompareFunctionsTestUtility.checkTargetFunctions(provider0, foo, foo, bar); + CompareFunctionsTestUtility.checkTargetFunctions(provider0, bar, foo, bar); + } + + /** + * + * Tests for {@link FunctionComparisonService#compareFunctions(Function, Function)} + * + */ + + @Test + public void testDualCompare() { + provider = plugin.compareFunctions(foo, bar); + CompareFunctionsTestUtility.checkSourceFunctions(provider, foo); + CompareFunctionsTestUtility.checkTargetFunctions(provider, foo, bar); + } + + /** + * + * Tests for {@link FunctionComparisonService#compareFunctions(Function, Function, FunctionComparisonProvider)} + * + */ + + @Test + public void testDualCompareAddToExisting() { + provider = plugin.compareFunctions(foo, bar); + plugin.compareFunctions(foo, two, provider); + + CompareFunctionsTestUtility.checkSourceFunctions(provider, foo); + CompareFunctionsTestUtility.checkTargetFunctions(provider, foo, bar, two); + } + + /** + * + * Data Model tests + * + */ + + @Test + public void testGetTargets() { + Set targets = model.getTargetFunctions(); + assertTrue(targets.size() == 6); + assertTrue(targets.contains(bar)); + assertTrue(targets.contains(two)); + assertTrue(targets.contains(three)); + assertTrue(targets.contains(four)); + assertTrue(targets.contains(five)); + assertTrue(targets.contains(stuff)); + } + + @Test + public void testGetTargetsForSource() { + Set targets = model.getTargetFunctions(bar); + assertTrue(targets.size() == 3); + assertTrue(targets.contains(three)); + assertTrue(targets.contains(four)); + assertTrue(targets.contains(five)); + } + + @Test + public void getSources() { + Set sources = model.getSourceFunctions(); + assertTrue(sources.size() == 3); + assertTrue(sources.contains(foo)); + assertTrue(sources.contains(bar)); + assertTrue(sources.contains(junk)); + } + + @Test + public void testRemoveFunctionFromModel() { + model.removeFunction(bar); + + Set sources = model.getSourceFunctions(); + assertTrue(sources.size() == 2); + assertTrue(sources.contains(foo)); + assertTrue(sources.contains(junk)); + + Set targets = model.getTargetFunctions(foo); + assertTrue(targets.size() == 1); + assertTrue(targets.contains(two)); + + targets = model.getTargetFunctions(junk); + assertTrue(targets.size() == 1); + assertTrue(targets.contains(stuff)); + } + + private ProgramBuilder buildTestProgram1() throws Exception { + ProgramBuilder builder = new ProgramBuilder("TestPgm1", ProgramBuilder._TOY_BE); + builder.createMemory(".text", "0x1001000", 0x6600); + builder.setProperty(Program.DATE_CREATED, new Date(100000000)); // arbitrary, but consistent + + // functions + DataType dt = new ByteDataType(); + Parameter p = new ParameterImpl(null, dt, builder.getProgram()); + foo = builder.createEmptyFunction("Foo", "10018cf", 10, null, p); + bar = builder.createEmptyFunction("Bar", "100299e", 130, null, p, p, p); + junk = builder.createEmptyFunction("Junk", "1002cf5", 15, null, p, p, p, p, p); + stuff = builder.createEmptyFunction("Stuff", "1003100", 20, null, p, p); + + program1 = builder.getProgram(); + AbstractGenericTest.setInstanceField("recordChanges", program1, Boolean.TRUE); + return builder; + } + + private ProgramBuilder buildTestProgram2() throws Exception { + ProgramBuilder builder = new ProgramBuilder("TestPgm2", ProgramBuilder._TOY64_BE); + builder.createMemory(".text", "0x1001000", 0x6600); + builder.setProperty(Program.DATE_CREATED, new Date(100000000)); // arbitrary, but consistent + + // functions + DataType dt = new ByteDataType(); + Parameter p = new ParameterImpl(null, dt, builder.getProgram()); + one = builder.createEmptyFunction("One", "10017c5", 10, null, p); + two = builder.createEmptyFunction("Two", "1001822", 130, null, p, p, p); + three = builder.createEmptyFunction("Three", "1001944", 15, null, p, p, p, p, p); + four = builder.createEmptyFunction("Four", "1002100", 20, null, p, p); + five = builder.createEmptyFunction("Five", "1002200", 20, null, p, p); + + program2 = builder.getProgram(); + AbstractGenericTest.setInstanceField("recordChanges", program2, Boolean.TRUE); + return builder; + } + + private FunctionComparisonModel createTestModel() { + FunctionComparisonModel model = new FunctionComparisonModel(); + + FunctionComparison c1 = new FunctionComparison(); + c1.setSource(foo); + c1.addTarget(bar); + c1.addTarget(two); + model.addComparison(c1); + + FunctionComparison c2 = new FunctionComparison(); + c2.setSource(bar); + c2.addTarget(three); + c2.addTarget(four); + c2.addTarget(five); + model.addComparison(c2); + + FunctionComparison c3 = new FunctionComparison(); + c3.setSource(junk); + c3.addTarget(stuff); + model.addComparison(c3); + + return model; + } +} diff --git a/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/functioncompare/CompareFunctionsTestSlow.java b/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/functioncompare/CompareFunctionsTestSlow.java new file mode 100644 index 0000000000..374371621d --- /dev/null +++ b/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/functioncompare/CompareFunctionsTestSlow.java @@ -0,0 +1,234 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.functioncompare; + +import static org.junit.Assert.assertNotNull; + +import java.awt.Window; +import java.util.Date; +import java.util.Set; + +import javax.swing.JPanel; + +import org.junit.*; + +import docking.action.DockingActionIf; +import docking.widgets.dialogs.TableChooserDialog; +import docking.widgets.table.GFilterTable; +import generic.test.AbstractGenericTest; +import ghidra.app.plugin.core.functionwindow.FunctionRowObject; +import ghidra.app.plugin.core.functionwindow.FunctionTableModel; +import ghidra.program.database.ProgramBuilder; +import ghidra.program.model.data.ByteDataType; +import ghidra.program.model.data.DataType; +import ghidra.program.model.listing.*; +import ghidra.test.AbstractGhidraHeadedIntegrationTest; +import ghidra.test.TestEnv; + +/** + * Tests for the {@link FunctionComparisonPlugin function comparison plugin} + * that involve the GUI + */ +public class CompareFunctionsTestSlow extends AbstractGhidraHeadedIntegrationTest { + + private TestEnv env; + private Program program1; + private Program program2; + private Function foo; + private Function bar; + private Function bat; + private FunctionComparisonPlugin plugin; + private FunctionComparisonProvider provider; + + @Before + public void setUp() throws Exception { + env = new TestEnv(); + plugin = env.addPlugin(FunctionComparisonPlugin.class); + assertNotNull(plugin); + buildTestProgram1(); + buildTestProgram2(); + showTool(plugin.getTool()); + env.open(program1); + } + + @After + public void tearDown() throws Exception { + env.dispose(); + } + + @Test + public void testRemoveLastItem() throws Exception { + Set functions = CompareFunctionsTestUtility.getFunctionsAsSet(foo); + provider = plugin.compareFunctions(functions); + provider = waitForComponentProvider(FunctionComparisonProvider.class); + plugin.removeFunction(foo, provider); + assert (!provider.isVisible()); + } + + @Test + public void testCloseProgram() throws Exception { + Set functions = CompareFunctionsTestUtility.getFunctionsAsSet(foo, bar); + provider = plugin.compareFunctions(functions); + + CompareFunctionsTestUtility.checkSourceFunctions(provider, foo, bar); + CompareFunctionsTestUtility.checkTargetFunctions(provider, foo, foo, bar); + CompareFunctionsTestUtility.checkTargetFunctions(provider, bar, foo, bar); + + plugin.programClosed(program1); + + CompareFunctionsTestUtility.checkSourceFunctions(provider, bar); + CompareFunctionsTestUtility.checkTargetFunctions(provider, bar, bar); + + plugin.programClosed(program2); + + CompareFunctionsTestUtility.checkSourceFunctions(provider); + } + + @Test + public void testNextPreviousAction() { + Set functions = CompareFunctionsTestUtility.getFunctionsAsSet(foo, bar); + provider = plugin.compareFunctions(functions); + provider.setVisible(true); + + waitForSwing(); + + // Must do this or there will be no "active" provider in the actions + // initiated below + clickComponentProvider(provider); + + DockingActionIf nextAction = getAction(plugin, "Compare Next Function"); + DockingActionIf prevAction = getAction(plugin, "Compare Previous Function"); + + assert (nextAction.isEnabled()); + assert (!prevAction.isEnabled()); + performAction(nextAction); + assert (!nextAction.isEnabled()); + assert (prevAction.isEnabled()); + } + + @Test + public void testNextPreviousActionSwitchPanelFocus() { + Set functions = CompareFunctionsTestUtility.getFunctionsAsSet(foo, bar); + provider = plugin.compareFunctions(functions); + provider.setVisible(true); + + waitForSwing(); + + // Must do this or there will be no "active" provider in the actions + // initiated below + clickComponentProvider(provider); + + DockingActionIf nextAction = getAction(plugin, "Compare Next Function"); + DockingActionIf prevAction = getAction(plugin, "Compare Previous Function"); + + assert (nextAction.isEnabled()); + assert (!prevAction.isEnabled()); + performAction(nextAction); + assert (!nextAction.isEnabled()); + assert (prevAction.isEnabled()); + + JPanel rightPanel = + provider.getComponent().getDualListingPanel().getRightPanel().getFieldPanel(); + clickMouse(rightPanel, 1, 30, 30, 1, 0); + waitForSwing(); + provider.getComponent().updateActionEnablement(); + + assert (nextAction.isEnabled()); + assert (!prevAction.isEnabled()); + } + + @Test + public void testOpenFunctionTableActionForAdd() { + Set functions = CompareFunctionsTestUtility.getFunctionsAsSet(foo, bar); + provider = plugin.compareFunctions(functions); + provider.setVisible(true); + + DockingActionIf openTableAction = getAction(plugin, "Add Functions To Comparison"); + performAction(openTableAction); + + Window selectWindow = waitForWindowByTitleContaining("Select Functions"); + assert (selectWindow != null); + } + + @SuppressWarnings("unchecked") + @Test + public void testAddFunctionToExistingCompare() { + Set functions = CompareFunctionsTestUtility.getFunctionsAsSet(foo); + provider = plugin.compareFunctions(functions); + provider.setVisible(true); + + waitForSwing(); + + // Must do this or there will be no "active" provider in the actions + // initiated below + clickComponentProvider(provider); + + assert (provider.getModel().getSourceFunctions().size() == 1); + + DockingActionIf openTableAction = getAction(plugin, "Add Functions To Comparison"); + performAction(openTableAction); + + TableChooserDialog chooser = + waitForDialogComponent(TableChooserDialog.class); + assert (chooser != null); + + GFilterTable table = + (GFilterTable) getInstanceField("gFilterTable", chooser); + assert (table.getModel().getRowCount() == 2); + clickTableCell(table.getTable(), 1, 0, 1); + + pressButtonByText(chooser, "OK"); + waitForSwing(); + assert (provider.getModel().getSourceFunctions().size() == 2); + } + + /** + * Builds a program with 2 functions + */ + private ProgramBuilder buildTestProgram1() throws Exception { + ProgramBuilder builder = new ProgramBuilder("TestPgm1", ProgramBuilder._TOY_BE); + builder.createMemory(".text", "0x1001000", 0x6600); + builder.setProperty(Program.DATE_CREATED, new Date(100000000)); // arbitrary, but consistent + + // functions + DataType dt = new ByteDataType(); + Parameter p = new ParameterImpl(null, dt, builder.getProgram()); + foo = builder.createEmptyFunction("Foo", "10018cf", 10, null, p); + bat = builder.createEmptyFunction("Bar", "100299e", 130, null, p, p, p); + + program1 = builder.getProgram(); + AbstractGenericTest.setInstanceField("recordChanges", program1, Boolean.TRUE); + return builder; + } + + /** + * Builds a program with 1 function + */ + private ProgramBuilder buildTestProgram2() throws Exception { + ProgramBuilder builder = new ProgramBuilder("TestPgm1", ProgramBuilder._TOY_BE); + builder.createMemory(".text", "0x1001000", 0x6600); + builder.setProperty(Program.DATE_CREATED, new Date(100000000)); // arbitrary, but consistent + + // functions + DataType dt = new ByteDataType(); + Parameter p = new ParameterImpl(null, dt, builder.getProgram()); + bar = builder.createEmptyFunction("Bar", "10018cf", 10, null, p); + + program2 = builder.getProgram(); + AbstractGenericTest.setInstanceField("recordChanges", program2, Boolean.TRUE); + return builder; + } +} diff --git a/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/functioncompare/CompareFunctionsTestUtility.java b/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/functioncompare/CompareFunctionsTestUtility.java new file mode 100644 index 0000000000..ac7e045129 --- /dev/null +++ b/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/functioncompare/CompareFunctionsTestUtility.java @@ -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.plugin.core.functioncompare; + +import java.util.*; + +import ghidra.program.model.listing.Function; + +/** + * Helper methods for use with function comparison tests + * + * @see {@link CompareFunctionsTest} + * @see {@link CompareFunctionsTestSlow} + */ +public class CompareFunctionsTestUtility { + + /** + * Asserts that a given list of functions represents all of the source + * functions in a comparison model + * + * @param provider the function comparison provider + * @param functions the source functions + */ + public static void checkSourceFunctions(FunctionComparisonProvider provider, + Function... functions) { + Set funcs = new HashSet<>(Arrays.asList(functions)); + Set fcs = provider.getModel().getSourceFunctions(); + assert (fcs.size() == funcs.size()); + assert (fcs.containsAll(funcs)); + } + + /** + * Asserts that a given function (source) is mapped to a collection of + * functions (targets) in a comparison model + * + * @param provider the function comparison provider + * @param source the source function + * @param targets the target functions + */ + public static void checkTargetFunctions(FunctionComparisonProvider provider, + Function source, Function... targets) { + Set targetsAsList = new HashSet<>(Arrays.asList(targets)); + Set tgts = provider.getModel().getTargetFunctions(source); + assert (tgts.size() == targetsAsList.size()); + assert (tgts.containsAll(targetsAsList)); + } + + /** + * Returns the given functions as a {@link Set} + * + * @param functions the functions to return as a set + * @return a set of functions + */ + public static Set getFunctionsAsSet(Function... functions) { + Set set = new HashSet<>(); + set.addAll(Arrays.asList(functions)); + return set; + } + + /** + * Returns the given functions as a {@link Map} of a function (source) to + * a set of functions (targets) + * + * @param source the key of the map + * @param targets the value of the map + * @return a map of a function to a set of functions + */ + public static Map> getFunctionsAsMap(Function source, + Function... targets) { + Set targetSet = getFunctionsAsSet(targets); + Map> map = new HashMap<>(); + map.put(source, targetSet); + return map; + } +} diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ApplyFunctionSignatureAction.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ApplyFunctionSignatureAction.java index 9b9159a467..7360831b65 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ApplyFunctionSignatureAction.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ApplyFunctionSignatureAction.java @@ -17,7 +17,7 @@ package ghidra.app.decompiler.component; import docking.ActionContext; import docking.widgets.fieldpanel.internal.FieldPanelCoordinator; -import ghidra.app.plugin.core.functioncompare.AbstractApplyFunctionSignatureAction; +import ghidra.app.plugin.core.functioncompare.actions.AbstractApplyFunctionSignatureAction; import ghidra.app.util.viewer.util.CodeComparisonPanel; /** diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerCodeComparisonPanel.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerCodeComparisonPanel.java index 7b8c49459e..24bd516695 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerCodeComparisonPanel.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerCodeComparisonPanel.java @@ -41,7 +41,7 @@ import ghidra.program.util.ProgramLocation; import ghidra.util.HTMLUtilities; /** - * Panel that displays two decompilers for comparison. + * Panel that displays two decompilers for comparison */ public abstract class DecompilerCodeComparisonPanel extends CodeComparisonPanel { @@ -68,7 +68,8 @@ public abstract class DecompilerCodeComparisonPanel extends DialogComponentProvider { private GTable gTable; private T selectedValue; + private List selectedValues = new ArrayList<>(); private GTableFilterPanel filterPanel; private RowObjectTableModel model; @@ -55,10 +57,15 @@ public class ListSelectionTableDialog extends DialogComponentProvider { @Override protected void okCallback() { - int selectedRow = gTable.getSelectedRow(); - if (selectedRow >= 0) { - int modelRow = filterPanel.getModelRow(selectedRow); - selectedValue = model.getRowObject(modelRow); + int[] selectedRows = gTable.getSelectedRows(); + if (selectedRows.length > 0) { + selectedValues.clear(); + for (int selectedRow : selectedRows) { + int modelRow = filterPanel.getModelRow(selectedRow); + T rowObject = model.getRowObject(modelRow); + selectedValues.add(rowObject); + } + selectedValue = selectedValues.isEmpty() ? null : selectedValues.get(0); close(); } } @@ -101,15 +108,26 @@ public class ListSelectionTableDialog extends DialogComponentProvider { return selectedValue; } + public List getSelectedItems() { + return selectedValues; + } + public T show(Component parent) { + setMultiSelectionMode(false); DockingWindowManager.showDialog(parent, this); return getSelectedItem(); } + public List showSelectMultiple(Component parent) { + setMultiSelectionMode(true); + DockingWindowManager.showDialog(parent, this); + return getSelectedItems(); + } + public void setMultiSelectionMode(boolean enable) { if (enable) { - gTable.getSelectionModel().setSelectionMode( - ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); + gTable.getSelectionModel() + .setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); } else { gTable.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/dialogs/TableChooserDialog.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/dialogs/TableChooserDialog.java new file mode 100644 index 0000000000..b7e29405da --- /dev/null +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/dialogs/TableChooserDialog.java @@ -0,0 +1,128 @@ +/* ### + * 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 docking.widgets.dialogs; + +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.Arrays; +import java.util.List; + +import javax.swing.*; + +import docking.DialogComponentProvider; +import docking.widgets.table.*; + +/** + * Dialog for displaying table data in a dialog for the purpose of the user selecting one or + * more items from the table. + * + * @param The type of row object in the table. + */ +public class TableChooserDialog extends DialogComponentProvider { + + private RowObjectTableModel model; + private GFilterTable gFilterTable; + private List selectedItems; + + /** + * Create a new Dialog for displaying and choosing table row items + * + * @param title The title for the dialog + * @param model a {@link RowObjectTableModel} that has the tRable data + * @param allowMultipleSelection if true, the dialog allows the user to select more + * than one row; otherwise, only single selection is allowed + */ + public TableChooserDialog(String title, RowObjectTableModel model, + boolean allowMultipleSelection) { + super(title); + this.model = model; + addWorkPanel(buildTable(allowMultipleSelection)); + addOKButton(); + addCancelButton(); + } + + /** + * Returns the list of selected items or null if the dialog was cancelled. + * @return the list of selected items or null if the dialog was cancelled. + */ + public List getSelectionItems() { + return selectedItems; + } + + private void initializeTable(boolean allowMultipleSelection) { + GTable table = gFilterTable.getTable(); + + table.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN); + + int selectionMode = allowMultipleSelection ? ListSelectionModel.MULTIPLE_INTERVAL_SELECTION + : ListSelectionModel.SINGLE_SELECTION; + table.getSelectionModel().setSelectionMode(selectionMode); + + } + + protected void processMouseClicked(MouseEvent e) { + + if (e.getClickCount() != 2) { + return; + } + + int rowAtPoint = gFilterTable.getTable().rowAtPoint(e.getPoint()); + if (rowAtPoint < 0) { + return; + } + + T selectedRowObject = gFilterTable.getSelectedRowObject(); + selectedItems = Arrays.asList(selectedRowObject); + close(); + } + + @Override + protected void okCallback() { + selectedItems = gFilterTable.getSelectedRowObjects(); + close(); + } + + @Override + protected void cancelCallback() { + selectedItems = null; + close(); + } + + @Override + protected void dialogShown() { + gFilterTable.focusFilter(); + } + + private JComponent buildTable(boolean allowMultipleSelection) { + gFilterTable = new GFilterTable<>(model); + initializeTable(allowMultipleSelection); + gFilterTable.getTable().addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if (!e.isShiftDown()) { + processMouseClicked(e); + } + updateOkEnabled(); + } + }); + setOkEnabled(false); + return gFilterTable; + } + + protected void updateOkEnabled() { + setOkEnabled(gFilterTable.getSelectedRowObject() != null); + } +} diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/internal/LineLockedFieldPanelCoordinator.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/internal/LineLockedFieldPanelCoordinator.java index d9d6717b37..0125872949 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/internal/LineLockedFieldPanelCoordinator.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/internal/LineLockedFieldPanelCoordinator.java @@ -15,13 +15,12 @@ */ package docking.widgets.fieldpanel.internal; -import ghidra.util.exception.AssertException; - import java.math.BigInteger; import java.util.ArrayList; import java.util.List; import docking.widgets.fieldpanel.FieldPanel; +import ghidra.util.exception.AssertException; /** * A LineLockedFieldPanelCoordinator coordinates the scrolling of a set of field panels by sharing @@ -63,12 +62,12 @@ public class LineLockedFieldPanelCoordinator extends FieldPanelCoordinator { public void setLockedLines(BigInteger[] lockedLineNumbers) { if (lockedLineNumbers.length != this.lockedLineNumbers.length) { throw new AssertException("The number of lines(" + lockedLineNumbers.length + - ") must exactly match the number of panels(" + this.lockedLineNumbers.length + ")."); + ") must exactly match the number of panels(" + this.lockedLineNumbers.length + + ")."); } for (int i = 0; i < lockedLineNumbers.length; i++) { if (lockedLineNumbers[i] == null) { - throw new AssertException("lockedLineNumber for field panel [" + i + - "] was unexpectedly null."); + lockedLineNumbers[i] = BigInteger.ZERO; } } for (int i = 0; i < lockedLineNumbers.length; i++) { @@ -99,7 +98,7 @@ public class LineLockedFieldPanelCoordinator extends FieldPanelCoordinator { */ @Override public void remove(FieldPanel fp) { - List lineNumberList = new ArrayList(panels.length); + List lineNumberList = new ArrayList<>(panels.length); // Adjust our locked line number array. int length = panels.length; for (int i = 0; i < length; i++) { diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/ComponentProviderAdapter.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/ComponentProviderAdapter.java index 98d7871407..e376d2fbb2 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/ComponentProviderAdapter.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/ComponentProviderAdapter.java @@ -46,7 +46,8 @@ public abstract class ComponentProviderAdapter extends ComponentProvider { * @param tool the plugin tool. * @param name The providers name. This is used to group similar providers into a tab within * the same window. - * @param owner The owner of this provider, usually a plugin name. + * @param owner The owner of this provider, usually a plugin name + * @param contextType the type of context supported by this provider; may be null */ public ComponentProviderAdapter(PluginTool tool, String name, String owner, Class contextType) { diff --git a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/FunctionComparisonScreenShots.java b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/FunctionComparisonScreenShots.java index 8aa29510c0..5f5f82310a 100644 --- a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/FunctionComparisonScreenShots.java +++ b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/FunctionComparisonScreenShots.java @@ -15,12 +15,15 @@ */ package help.screenshot; -import java.util.HashMap; +import java.awt.Rectangle; +import java.io.IOException; import java.util.HashSet; +import java.util.Set; import org.junit.Test; -import docking.ComponentProvider; +import docking.DialogComponentProvider; +import docking.action.DockingActionIf; import ghidra.app.cmd.disassemble.DisassembleCommand; import ghidra.app.plugin.core.functioncompare.*; import ghidra.app.util.viewer.listingpanel.ListingCodeComparisonPanel; @@ -30,8 +33,8 @@ import ghidra.program.model.address.AddressSet; import ghidra.program.model.listing.*; import ghidra.program.model.mem.Memory; import ghidra.program.model.mem.MemoryAccessException; -import ghidra.program.model.symbol.Namespace; import ghidra.program.model.symbol.SourceType; +import ghidra.util.InvalidNameException; import ghidra.util.exception.DuplicateNameException; import ghidra.util.exception.InvalidInputException; @@ -52,19 +55,27 @@ public class FunctionComparisonScreenShots extends GhidraScreenShotGenerator { public void setUp() throws Exception { super.setUp(); plugin = getPlugin(tool, FunctionComparisonPlugin.class); + + destinationProgram = loadProgram(TEST_DESTINATION_PROGRAM_NAME); + sourceProgram = loadProgram(TEST_SOURCE_PROGRAM_NAME); + } + + public void tearDown() throws Exception { + super.tearDown(); } @Test public void testFunctionComparisonWindow() { - destinationProgram = loadProgram(TEST_DESTINATION_PROGRAM_NAME); - sourceProgram = loadProgram(TEST_SOURCE_PROGRAM_NAME); positionListingTop(0x004118f0); int txId1 = sourceProgram.startTransaction("Modify Program1"); int txId2 = destinationProgram.startTransaction("Modify Program2"); try { - sourceProgram.setName("TestProgram"); - destinationProgram.setName("OtherProgram"); + sourceProgram.getDomainFile().setName("FirstProgram"); + destinationProgram.getDomainFile().setName("SecondProgram"); + sourceProgram.setName("FirstProgram"); + destinationProgram.setName("SecondProgram"); + Listing sourceListing = sourceProgram.getListing(); Listing destListing = destinationProgram.getListing(); Memory sourceMemory = sourceProgram.getMemory(); @@ -81,100 +92,10 @@ public class FunctionComparisonScreenShots extends GhidraScreenShotGenerator { f2.setName("FunctionB", SourceType.USER_DEFINED); destListing.setComment(addr(0x004118c0), CodeUnit.PLATE_COMMENT, null); - Function[] functions = new Function[] { f1, f2 }; FunctionComparisonProviderManager providerMgr = getInstanceFieldByClassType(FunctionComparisonProviderManager.class, plugin); FunctionComparisonProvider functionComparisonProvider = - providerMgr.showFunctionComparisonProvider(functions); - FunctionComparisonPanel functionComparisonPanel = - functionComparisonProvider.getComponent(); - runSwing(() -> { - functionComparisonPanel.setCurrentTabbedComponent("Listing View"); - ListingCodeComparisonPanel dualListing = - (ListingCodeComparisonPanel) functionComparisonPanel.getDisplayedPanel(); - ListingPanel leftPanel = dualListing.getLeftPanel(); - dualListing.setLeftTitle("FunctionA() in /TestProgram"); - dualListing.setRightTitle("FunctionB() in /OtherProgram"); - leftPanel.goTo(addr(0x004119aa)); - - }); - waitForSwing(); - captureIsolatedProvider(FunctionComparisonProvider.class, 1200, 550); - } - catch (DuplicateNameException e) { - e.printStackTrace(); - } - catch (InvalidInputException e) { - e.printStackTrace(); - } - catch (MemoryAccessException e) { - e.printStackTrace(); - } - finally { - destinationProgram.endTransaction(txId2, false); - sourceProgram.endTransaction(txId1, false); - } - } - - @Test - public void testFunctionComparisonWindowFromMap() throws CircularDependencyException { - destinationProgram = loadProgram(TEST_DESTINATION_PROGRAM_NAME); - sourceProgram = loadProgram(TEST_SOURCE_PROGRAM_NAME); - - positionListingTop(0x004118f0); - int txId1 = sourceProgram.startTransaction("Modify Program1"); - int txId2 = destinationProgram.startTransaction("Modify Program2"); - try { - sourceProgram.setName("TestProgram"); - destinationProgram.setName("OtherProgram"); - Listing sourceListing = sourceProgram.getListing(); - Listing destListing = destinationProgram.getListing(); - Memory sourceMemory = sourceProgram.getMemory(); - - Function f1 = getFunction(sourceProgram, addr(0x004118f0)); - f1.setName("Function1", SourceType.USER_DEFINED); - Namespace parentNamespace = sourceProgram.getSymbolTable().createNameSpace( - program.getGlobalNamespace(), "Namespace1", SourceType.USER_DEFINED); - f1.setParentNamespace(parentNamespace); - sourceListing.setComment(addr(0x004118f0), CodeUnit.PLATE_COMMENT, null); - sourceListing.clearCodeUnits(addr(0x004119b1), addr(0x004119b4), false); - sourceMemory.setByte(addr(0x004119b2), (byte) 0x55); - sourceMemory.setByte(addr(0x004119b4), (byte) 0x52); - disassemble(sourceProgram, 0x004119b1, 4, false); - - Function f2 = getFunction(destinationProgram, addr(0x004118c0)); - f2.setName("Function2", SourceType.USER_DEFINED); - destListing.setComment(addr(0x004118c0), CodeUnit.PLATE_COMMENT, null); - - Function fA = getFunction(sourceProgram, addr(0x00411a30)); - fA.setName("FunctionA", SourceType.USER_DEFINED); - sourceListing.setComment(addr(0x00411a30), CodeUnit.PLATE_COMMENT, null); - - Function fB = getFunction(destinationProgram, addr(0x00411a10)); - fB.setName("FunctionB", SourceType.USER_DEFINED); - destListing.setComment(addr(0x00411a10), CodeUnit.PLATE_COMMENT, null); - - Function fC = getFunction(sourceProgram, addr(0x00411ab0)); - fC.setName("FunctionC", SourceType.USER_DEFINED); - sourceListing.setComment(addr(0x00411ab0), CodeUnit.PLATE_COMMENT, null); - - Function fD = getFunction(destinationProgram, addr(0x00411a90)); - fD.setName("FunctionD", SourceType.USER_DEFINED); - destListing.setComment(addr(0x00411a90), CodeUnit.PLATE_COMMENT, null); - - HashMap> functionMap = new HashMap<>(); - HashSet functionSet = new HashSet<>(); - functionSet.add(fA); - functionSet.add(fB); - functionMap.put(f1, functionSet); - functionSet = new HashSet<>(); - functionSet.add(fC); - functionSet.add(fD); - functionMap.put(f2, functionSet); - FunctionComparisonProviderManager providerMgr = - getInstanceFieldByClassType(FunctionComparisonProviderManager.class, plugin); - FunctionComparisonProvider functionComparisonProvider = - providerMgr.showFunctionComparisonProvider(functionMap); + providerMgr.compareFunctions(f1, f2); FunctionComparisonPanel functionComparisonPanel = functionComparisonProvider.getComponent(); runSwing(() -> { @@ -183,12 +104,12 @@ public class FunctionComparisonScreenShots extends GhidraScreenShotGenerator { (ListingCodeComparisonPanel) functionComparisonPanel.getDisplayedPanel(); ListingPanel leftPanel = dualListing.getLeftPanel(); leftPanel.goTo(addr(0x004119aa)); - }); waitForSwing(); captureIsolatedProvider(FunctionComparisonProvider.class, 1200, 550); } - catch (DuplicateNameException | InvalidInputException | MemoryAccessException e) { + catch (DuplicateNameException | InvalidInputException | MemoryAccessException + | InvalidNameException | IOException e) { e.printStackTrace(); } finally { @@ -198,136 +119,102 @@ public class FunctionComparisonScreenShots extends GhidraScreenShotGenerator { } @Test - public void testListingCodeComparisonOptions() { - destinationProgram = loadProgram(TEST_DESTINATION_PROGRAM_NAME); - sourceProgram = loadProgram(TEST_SOURCE_PROGRAM_NAME); + public void testAddToComparisonIcon() { + Function f1 = getFunction(sourceProgram, addr(0x004118f0)); + Function f2 = getFunction(destinationProgram, addr(0x004118c0)); - positionListingTop(0x004118f0); - int txId1 = sourceProgram.startTransaction("Modify Program1"); - int txId2 = destinationProgram.startTransaction("Modify Program2"); - try { - sourceProgram.setName("TestProgram"); - destinationProgram.setName("OtherProgram"); - Listing sourceListing = sourceProgram.getListing(); - Listing destListing = destinationProgram.getListing(); - Memory sourceMemory = sourceProgram.getMemory(); + FunctionComparisonProviderManager providerMgr = + getInstanceFieldByClassType(FunctionComparisonProviderManager.class, plugin); + FunctionComparisonProvider functionComparisonProvider = + providerMgr.compareFunctions(f1, f2); - Function f1 = getFunction(sourceProgram, addr(0x004118f0)); - f1.setName("FunctionA", SourceType.USER_DEFINED); - sourceListing.setComment(addr(0x004118f0), CodeUnit.PLATE_COMMENT, null); - sourceListing.clearCodeUnits(addr(0x004119b1), addr(0x004119b4), false); - sourceMemory.setByte(addr(0x004119b2), (byte) 0x55); - sourceMemory.setByte(addr(0x004119b4), (byte) 0x52); - disassemble(sourceProgram, 0x004119b1, 4, false); + waitForSwing(); - Function f2 = getFunction(destinationProgram, addr(0x004118c0)); - f2.setName("FunctionB", SourceType.USER_DEFINED); - destListing.setComment(addr(0x004118c0), CodeUnit.PLATE_COMMENT, null); - - Function[] functions = new Function[] { f1, f2 }; - FunctionComparisonProviderManager providerMgr = - getInstanceFieldByClassType(FunctionComparisonProviderManager.class, plugin); - FunctionComparisonProvider functionComparisonProvider = - providerMgr.showFunctionComparisonProvider(functions); - FunctionComparisonPanel functionComparisonPanel = - functionComparisonProvider.getComponent(); - runSwing(() -> { - functionComparisonPanel.setCurrentTabbedComponent("Listing View"); - }); - waitForSwing(); - - ComponentProvider provider = getProvider("Function Comparison"); - performAction("Listing Code Comparison Options", "Function Comparison", provider, - false); - - captureDialog(600, 300); - pressButtonByText(getDialog(), "Cancel"); - - waitForSwing(); - } - catch (DuplicateNameException e) { - e.printStackTrace(); - } - catch (InvalidInputException e) { - e.printStackTrace(); - } - catch (MemoryAccessException e) { - e.printStackTrace(); - } - finally { - destinationProgram.endTransaction(txId2, false); - sourceProgram.endTransaction(txId1, false); - } + captureProvider(functionComparisonProvider); + crop(new Rectangle(261, 2, 24, 24)); } @Test - public void testMultiFunctionComparisonWindow() { - destinationProgram = loadProgram(TEST_DESTINATION_PROGRAM_NAME); - sourceProgram = loadProgram(TEST_SOURCE_PROGRAM_NAME); + public void testRemoveFromComparisonIcon() { + Function f1 = getFunction(sourceProgram, addr(0x004118f0)); + Function f2 = getFunction(destinationProgram, addr(0x004118c0)); - positionListingTop(0x004118f0); - int txId1 = sourceProgram.startTransaction("Modify Program1"); - int txId2 = destinationProgram.startTransaction("Modify Program2"); - try { - sourceProgram.setName("TestProgram"); - destinationProgram.setName("OtherProgram"); - Listing sourceListing = sourceProgram.getListing(); - Listing destListing = destinationProgram.getListing(); - Memory sourceMemory = sourceProgram.getMemory(); + FunctionComparisonProviderManager providerMgr = + getInstanceFieldByClassType(FunctionComparisonProviderManager.class, plugin); + FunctionComparisonProvider functionComparisonProvider = + providerMgr.compareFunctions(f1, f2); - Function f1 = getFunction(sourceProgram, addr(0x004118f0)); - f1.setName("FunctionA", SourceType.USER_DEFINED); - sourceListing.setComment(addr(0x004118f0), CodeUnit.PLATE_COMMENT, null); - sourceListing.clearCodeUnits(addr(0x004119b1), addr(0x004119b4), false); - sourceMemory.setByte(addr(0x004119b2), (byte) 0x55); - sourceMemory.setByte(addr(0x004119b4), (byte) 0x52); - disassemble(sourceProgram, 0x004119b1, 4, false); + waitForSwing(); - Function f2 = getFunction(destinationProgram, addr(0x004118c0)); - f2.setName("FunctionB", SourceType.USER_DEFINED); - destListing.setComment(addr(0x004118c0), CodeUnit.PLATE_COMMENT, null); - - Function f3 = getFunction(sourceProgram, addr(0x004117c0)); - f3.setName("FunctionC", SourceType.USER_DEFINED); - sourceListing.setComment(addr(0x004117c0), CodeUnit.PLATE_COMMENT, null); - - Function f4 = getFunction(destinationProgram, addr(0x004117b0)); - f4.setName("FunctionD", SourceType.USER_DEFINED); - destListing.setComment(addr(0x004117b0), CodeUnit.PLATE_COMMENT, null); - - Function[] functions = new Function[] { f1, f2, f3, f4 }; - FunctionComparisonProviderManager providerMgr = - getInstanceFieldByClassType(FunctionComparisonProviderManager.class, plugin); - FunctionComparisonProvider functionComparisonProvider = - providerMgr.showFunctionComparisonProvider(functions); - FunctionComparisonPanel functionComparisonPanel = - functionComparisonProvider.getComponent(); - runSwing(() -> { - functionComparisonPanel.setCurrentTabbedComponent("Listing View"); - ListingCodeComparisonPanel dualListing = - (ListingCodeComparisonPanel) functionComparisonPanel.getDisplayedPanel(); - ListingPanel leftPanel = dualListing.getLeftPanel(); - leftPanel.goTo(addr(0x004119a5)); - - }); - waitForSwing(); - captureIsolatedProvider(FunctionComparisonProvider.class, 1200, 598); - } - catch (DuplicateNameException | InvalidInputException | MemoryAccessException e) { - e.printStackTrace(); - } - finally { - destinationProgram.endTransaction(txId2, false); - sourceProgram.endTransaction(txId1, false); - } + captureProvider(functionComparisonProvider); + crop(new Rectangle(351, 2, 24, 24)); } - Function getFunction(Program program1, Address entryPoint) { + @Test + public void testNavNextIcon() { + Function f1 = getFunction(sourceProgram, addr(0x004118f0)); + Function f2 = getFunction(destinationProgram, addr(0x004118c0)); + + FunctionComparisonProviderManager providerMgr = + getInstanceFieldByClassType(FunctionComparisonProviderManager.class, plugin); + Set functions = new HashSet<>(); + functions.add(f1); + functions.add(f2); + FunctionComparisonProvider functionComparisonProvider = + providerMgr.compareFunctions(functions); + + waitForSwing(); + + captureProvider(functionComparisonProvider); + crop(new Rectangle(293, 2, 24, 24)); + } + + @Test + public void testNavPreviousIcon() { + Function f1 = getFunction(sourceProgram, addr(0x004118f0)); + Function f2 = getFunction(destinationProgram, addr(0x004118c0)); + + FunctionComparisonProviderManager providerMgr = + getInstanceFieldByClassType(FunctionComparisonProviderManager.class, plugin); + Set functions = new HashSet<>(); + functions.add(f1); + functions.add(f2); + FunctionComparisonProvider functionComparisonProvider = + providerMgr.compareFunctions(functions); + MultiFunctionComparisonPanel panel = + (MultiFunctionComparisonPanel) functionComparisonProvider.getComponent(); + panel.getFocusedComponent().setSelectedIndex(1); + + waitForSwing(); + + captureProvider(functionComparisonProvider); + crop(new Rectangle(315, 2, 24, 24)); + } + + @Test + public void testAddFunctionsPanel() { + Function f1 = getFunction(sourceProgram, addr(0x004118f0)); + Function f2 = getFunction(destinationProgram, addr(0x004118c0)); + + FunctionComparisonProviderManager providerMgr = + getInstanceFieldByClassType(FunctionComparisonProviderManager.class, plugin); + Set functions = new HashSet<>(); + functions.add(f1); + functions.add(f2); + providerMgr.compareFunctions(functions); + + DockingActionIf openTableAction = getAction(plugin, "Add Functions To Comparison"); + performAction(openTableAction); + DialogComponentProvider table = waitForDialogComponent("Select Functions"); + captureDialog(table); + } + + private Function getFunction(Program program1, Address entryPoint) { FunctionManager functionManager = program1.getFunctionManager(); return functionManager.getFunctionAt(entryPoint); } - public void disassemble(Program pgm1, long addressAsLong, int length, boolean followFlows) { + private void disassemble(Program pgm1, long addressAsLong, int length, boolean followFlows) { Address address = addr(addressAsLong); DisassembleCommand cmd = new DisassembleCommand(address, new AddressSet(address, address.add(length - 1)), followFlows);