Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a4badc5fd4 | ||
|
|
49b4b19c21 | ||
|
|
068520f923 | ||
|
|
f723ee3dee | ||
|
|
ce1f9181cf | ||
|
|
a043f93e72 | ||
|
|
c0cb4a5123 | ||
|
|
db64c517a4 | ||
|
|
9602d98307 | ||
|
|
d85739b5aa | ||
|
|
1828006c2b | ||
|
|
aff259387b | ||
|
|
fe80cff908 | ||
|
|
ae183f2c43 | ||
|
|
9ae80e4d62 | ||
|
|
e920c6aba8 | ||
|
|
5875f989d8 | ||
|
|
d95daf392f | ||
|
|
6c4997ac29 | ||
|
|
a8d239febd | ||
|
|
6b52aa0be1 | ||
|
|
19c3c07b99 | ||
|
|
ef8aba25e8 | ||
|
|
c2f042643a | ||
|
|
baf1afaca0 | ||
|
|
de8f2b476e | ||
|
|
bc6c362bd5 | ||
|
|
bff22b12bd | ||
|
|
63f2bd57cd | ||
|
|
5518aaa29d | ||
|
|
73856c6c32 | ||
|
|
018c210777 | ||
|
|
59a47845f0 | ||
|
|
11b530a526 | ||
|
|
f6b792020a | ||
|
|
c50dc3313f | ||
|
|
0e5a49c331 | ||
|
|
74c12685a0 | ||
|
|
93af6edfe9 | ||
|
|
977578a91e | ||
|
|
e980b1cb48 | ||
|
|
b5edef36bc | ||
|
|
1208624c77 | ||
|
|
eff435fc29 | ||
|
|
af1f536b0f | ||
|
|
6affec227d | ||
|
|
e0a120ad08 | ||
|
|
3383cc1d92 | ||
|
|
7eade2df0c | ||
|
|
bd1105f45d | ||
|
|
a7532b4d80 | ||
|
|
11240a1a0f | ||
|
|
ddb7bdb428 | ||
|
|
b912778e62 | ||
|
|
dc1fde07f5 | ||
|
|
1e089d9c37 | ||
|
|
e0e9690fd5 | ||
|
|
bbbea0adcd | ||
|
|
f8ef719e31 | ||
|
|
e560d5f88c | ||
|
|
3f9e2f78f9 | ||
|
|
d29c291ff7 | ||
|
|
e62e0a9d70 | ||
|
|
0ccc495308 | ||
|
|
95ca7a071f | ||
|
|
4e100c4fc3 | ||
|
|
abf90e6eaa | ||
|
|
dd28d5b367 | ||
|
|
d7fabe89f2 | ||
|
|
069b64679c | ||
|
|
7a8701f030 | ||
|
|
dafb693b59 | ||
|
|
8df94a0b91 | ||
|
|
e9c99b6da4 | ||
|
|
144dcce52f | ||
|
|
f6c5cf0cb8 | ||
|
|
c3ecfea9bb | ||
|
|
5c7dbaca9c | ||
|
|
2982ce12f9 | ||
|
|
eaf3b1ae36 | ||
|
|
c2da3b5c88 | ||
|
|
815267962d | ||
|
|
60da11e8d9 | ||
|
|
244596bcb0 | ||
|
|
27be4123c3 | ||
|
|
158ba57ae5 | ||
|
|
bb0c8ab6d4 | ||
|
|
e35d443079 | ||
|
|
55a401b3dc | ||
|
|
0ecf546b80 | ||
|
|
4a14b8ddfb | ||
|
|
61f47fca35 | ||
|
|
ab9eb65ac3 | ||
|
|
04ca1b2f13 | ||
|
|
24e410c41a | ||
|
|
9c0cf0116c | ||
|
|
3ea7715576 | ||
|
|
455617e037 | ||
|
|
0ff5a468e5 | ||
|
|
fb1b22c35f | ||
|
|
f67b3a624b | ||
|
|
146b28d184 | ||
|
|
f90e9931bb | ||
|
|
36702df4d2 | ||
|
|
e5e991eef1 | ||
|
|
5a2cacbb22 | ||
|
|
9ec68f2426 | ||
|
|
37c69f1be1 | ||
|
|
4907be5edb | ||
|
|
a426633df6 | ||
|
|
585abd51ea | ||
|
|
bc98912424 | ||
|
|
8c54b1650e | ||
|
|
fba1753457 | ||
|
|
7633fc891c | ||
|
|
976b41ffd5 | ||
|
|
d647d4291f | ||
|
|
45e895d746 | ||
|
|
c5b3632379 | ||
|
|
9ebea99c80 | ||
|
|
bd90af5a2a | ||
|
|
f48325b11e | ||
|
|
7a982fb681 | ||
|
|
da160271ad | ||
|
|
db945fc9b1 | ||
|
|
59c69f21c9 | ||
|
|
0dc50a7306 | ||
|
|
33223ed594 | ||
|
|
ed44971875 | ||
|
|
391d1d385e | ||
|
|
acf34585a5 | ||
|
|
7920ad3be6 | ||
|
|
67c2ccdb89 | ||
|
|
2638a61b59 | ||
|
|
cfc83bc92f | ||
|
|
4528457e93 | ||
|
|
60671f810d | ||
|
|
8bd4789a00 | ||
|
|
1a4ae4b22b | ||
|
|
2d3ab3cd46 | ||
|
|
eb89ac2829 | ||
|
|
6b9f07e10b | ||
|
|
323b6a77f2 | ||
|
|
b75507bafd | ||
|
|
c0041e087b | ||
|
|
9f9defe7fe | ||
|
|
6f06f58c24 | ||
|
|
0a04cfaeee | ||
|
|
b2bd4b3ff6 | ||
|
|
3048aa9634 | ||
|
|
b69358b7a6 | ||
|
|
0b89fa479e | ||
|
|
b4fb07fee7 | ||
|
|
837f85f0b2 | ||
|
|
3c71473e14 | ||
|
|
2cc4e4eb66 | ||
|
|
79123dd623 | ||
|
|
72889bcd2d | ||
|
|
8a1316d2bc | ||
|
|
b6eee838dd | ||
|
|
6bdf44423d | ||
|
|
a3b9012ee1 | ||
|
|
d9e5c17fda | ||
|
|
659f72ae8c | ||
|
|
9393a5f8a4 | ||
|
|
82b7ff33f2 | ||
|
|
bb29169e5c | ||
|
|
d2c228892f | ||
|
|
b6f8b69f54 | ||
|
|
a8fa25b49e | ||
|
|
15acc10df5 | ||
|
|
0f3dbdbe96 | ||
|
|
90f598bbe5 | ||
|
|
92816c58b0 | ||
|
|
fdaa11ba07 | ||
|
|
65ef2a6f2c | ||
|
|
13c4522523 | ||
|
|
1f9e0c4ad0 | ||
|
|
e8f7965a36 | ||
|
|
f7deacbf8d | ||
|
|
1352d7f368 | ||
|
|
c0982c368a | ||
|
|
81c9e6efbb | ||
|
|
8bdea890ee | ||
|
|
fb3f35f28c | ||
|
|
9ddfcfa67a | ||
|
|
5a6965f47f | ||
|
|
2375092641 | ||
|
|
20b46190f7 | ||
|
|
9bf15002d2 | ||
|
|
19e442b02b | ||
|
|
1462ab8605 | ||
|
|
a448f2e439 | ||
|
|
8d6511b0bb | ||
|
|
09bcc04457 | ||
|
|
840c053b78 | ||
|
|
b15f643175 | ||
|
|
fedf119f3d | ||
|
|
a58c64e19d | ||
|
|
ca5aba0b88 | ||
|
|
a06ee3a53f | ||
|
|
71501a5f9e | ||
|
|
021e6e7dd5 | ||
|
|
f00bd996b8 | ||
|
|
4e4ed4bf4e | ||
|
|
219b8bd347 | ||
|
|
c4744cf05d | ||
|
|
9847074e66 | ||
|
|
74142d0960 | ||
|
|
637cc91e05 | ||
|
|
09c4042b0a | ||
|
|
cb865912a4 | ||
|
|
f066af54c6 | ||
|
|
5d96c27c49 | ||
|
|
9dba69e73e | ||
|
|
fbcc33a107 | ||
|
|
43ee34e96b | ||
|
|
3199881132 | ||
|
|
5579b69d57 | ||
|
|
ce3e581e60 | ||
|
|
b49bff7695 | ||
|
|
b6e20cc15e | ||
|
|
96eb17e0c8 | ||
|
|
0d7396d141 | ||
|
|
1a50c804ff | ||
|
|
dbafec2394 | ||
|
|
0e6a4173d6 | ||
|
|
29ca4e870e | ||
|
|
43fa969337 | ||
|
|
9125397762 | ||
|
|
253de4ffd3 | ||
|
|
bafa30848e | ||
|
|
904d4e98f4 | ||
|
|
92c0005e89 | ||
|
|
d72ae5faec | ||
|
|
5c61582364 | ||
|
|
cbb0c53abf | ||
|
|
6483c8b4a6 | ||
|
|
48b2f74b12 | ||
|
|
028dbab360 | ||
|
|
3348f0f3f8 | ||
|
|
a071b97c45 | ||
|
|
a0a7b54a95 | ||
|
|
1e6d80ed95 | ||
|
|
d8eec7cc1f | ||
|
|
e0aec1f7aa | ||
|
|
b6a210fc67 | ||
|
|
0d86970f84 | ||
|
|
836d32c8d9 | ||
|
|
217d001177 | ||
|
|
c1c9300a25 | ||
|
|
b4a75222fd | ||
|
|
0e113bb25e | ||
|
|
2ea419e00a | ||
|
|
9920147530 | ||
|
|
05877345db | ||
|
|
1480a14414 | ||
|
|
de683ce850 | ||
|
|
55130697c5 | ||
|
|
03fb805a89 | ||
|
|
c251b496b2 | ||
|
|
c574e93208 | ||
|
|
44d7b76eb1 | ||
|
|
a0d3dcabdd | ||
|
|
50465c2827 | ||
|
|
636a326591 | ||
|
|
258c9c50bb | ||
|
|
ba715d93a8 | ||
|
|
a2ca4364e6 | ||
|
|
060400b375 | ||
|
|
f2055bb413 | ||
|
|
8dd8bea745 | ||
|
|
e7ac2795cd | ||
|
|
aadb3e53a3 | ||
|
|
0619f6aa6e | ||
|
|
c1c885541d | ||
|
|
96d2099036 | ||
|
|
5a02c74900 | ||
|
|
e2f04b4cb0 | ||
|
|
318636ce8c | ||
|
|
c401fd77e9 | ||
|
|
d6c44e3393 | ||
|
|
45120ece4a | ||
|
|
e61e731656 | ||
|
|
f2cee8a338 | ||
|
|
e58a412918 | ||
|
|
94e3f77731 | ||
|
|
8e41ec7543 | ||
|
|
02f952cef3 | ||
|
|
c9c55b00db | ||
|
|
4d611f4024 | ||
|
|
3b4da08524 | ||
|
|
0e08185050 | ||
|
|
696584caa7 | ||
|
|
c0c9f1bbfe | ||
|
|
e684efc225 | ||
|
|
31ded012fb | ||
|
|
d253413d73 | ||
|
|
345523e2c6 | ||
|
|
b69946ad36 | ||
|
|
e649769d5c | ||
|
|
33e09b50ea | ||
|
|
c00b73bc35 | ||
|
|
ba5c3251f8 | ||
|
|
e6e9dddba2 | ||
|
|
d47b0cbeaf | ||
|
|
94a7c8863f | ||
|
|
dfdeaf1836 | ||
|
|
0a35580620 | ||
|
|
302bbff3a3 | ||
|
|
f66dacf430 | ||
|
|
c7d1f4be3d | ||
|
|
44848d44de | ||
|
|
ce18a5ed50 | ||
|
|
179391c4e0 | ||
|
|
7bb6e48ca7 | ||
|
|
5263d67cc3 | ||
|
|
df49d29e83 | ||
|
|
d585b0889a | ||
|
|
dc268a9f12 | ||
|
|
97f38c6ae5 | ||
|
|
0b73d86f69 | ||
|
|
47f71cb7e5 | ||
|
|
bb9f6743dc | ||
|
|
b071ea0124 | ||
|
|
7dfd907c1c | ||
|
|
ca916f9a16 | ||
|
|
b67d2d2320 | ||
|
|
5aa3181ffd | ||
|
|
4d48a1ff69 | ||
|
|
39a389405f | ||
|
|
8ee7fab4bd | ||
|
|
0f58b79a4a | ||
|
|
b415c514aa | ||
|
|
9869ab8744 | ||
|
|
c5811fbfbb | ||
|
|
8b79f526a7 | ||
|
|
a0ce5356a8 | ||
|
|
418b9be377 | ||
|
|
d6da908caf | ||
|
|
e5410cf323 | ||
|
|
ffcf9646e8 | ||
|
|
46d038f244 | ||
|
|
58369fb768 | ||
|
|
a66a8c9780 | ||
|
|
1f762503aa | ||
|
|
fbc4561220 | ||
|
|
5b1f63fa0d | ||
|
|
6687c73f0f | ||
|
|
4917f52db1 | ||
|
|
d88ba52ec5 | ||
|
|
743b515950 | ||
|
|
f6845f0c83 | ||
|
|
3dfebf68bc | ||
|
|
542a3f4de5 | ||
|
|
a48b74e9d3 | ||
|
|
4284126fb9 | ||
|
|
790878048e | ||
|
|
11449f4514 | ||
|
|
3fa08ede74 | ||
|
|
d2987d6f3b | ||
|
|
7aa0b9b2e3 | ||
|
|
3d49d12b2d | ||
|
|
14bd8e68cb | ||
|
|
ce618fb021 | ||
|
|
58d80f39e3 | ||
|
|
04c3675d38 | ||
|
|
760ab5f266 | ||
|
|
08b3be3c7e | ||
|
|
6448a18076 | ||
|
|
ce04d6500b | ||
|
|
8e84add23b | ||
|
|
db4351626e | ||
|
|
ff93981bd9 | ||
|
|
94d799d6a3 | ||
|
|
cb47b42a8e | ||
|
|
186f91a0a5 | ||
|
|
3ea208938c | ||
|
|
7eb816fdb0 | ||
|
|
e71c630153 | ||
|
|
cf5b1dbee7 | ||
|
|
1636f59f9d | ||
|
|
b7c1c507d6 | ||
|
|
1b5984c0ae | ||
|
|
5f44a3259b | ||
|
|
868a7e732f | ||
|
|
492552cbe6 | ||
|
|
5780bbd265 | ||
|
|
5068a0be61 | ||
|
|
83e7a8513b | ||
|
|
ce0334c695 | ||
|
|
3ea6674dfc | ||
|
|
f1f01e5d1a | ||
|
|
01d7ae86c3 | ||
|
|
1c49e49eff | ||
|
|
d3d4f54a7d | ||
|
|
9d0294be7f | ||
|
|
9faa9e9b9d | ||
|
|
c85334f956 | ||
|
|
ef7467ebf7 | ||
|
|
27cdc1793c | ||
|
|
920a5b8386 | ||
|
|
d0bea6ebaa | ||
|
|
9a2f8ae70b | ||
|
|
da62c9a38c | ||
|
|
4982ddefbb | ||
|
|
ab823af695 | ||
|
|
93e3e13d2f | ||
|
|
18a01fba1a | ||
|
|
2cf5412424 | ||
|
|
a7f087aa82 | ||
|
|
72e754cdc2 | ||
|
|
6833013df8 | ||
|
|
997ba77421 | ||
|
|
7a514f6510 | ||
|
|
e16301a1b1 | ||
|
|
a107b234d8 | ||
|
|
1326aaca78 | ||
|
|
ea8f224aa3 | ||
|
|
6ffbef59c5 | ||
|
|
c89a4afd9f | ||
|
|
d856ea5e91 | ||
|
|
6b4d9663be | ||
|
|
1e6e664137 | ||
|
|
e9512807e4 | ||
|
|
6b6caf3303 | ||
|
|
fa08199f43 |
No files matched your search
@@ -1,9 +1,18 @@
|
||||
name: iaito CI
|
||||
|
||||
env:
|
||||
R2V: 6.0.4
|
||||
R2V: 6.2.6
|
||||
R2GHIDRA_VERSION: 6.2.6
|
||||
QT_MAC_INSTALL_VERSION: 6.11.*
|
||||
QT_WIN_INSTALL_VERSION: 6.10.*
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release_tag:
|
||||
description: Existing version tag to build and publish
|
||||
required: true
|
||||
type: string
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
@@ -12,14 +21,57 @@ on:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
acr-linux-r2git:
|
||||
runs-on: ubuntu-22.04
|
||||
source:
|
||||
runs-on: ubuntu-24.04
|
||||
outputs:
|
||||
sha: ${{ steps.source.outputs.sha }}
|
||||
r2: ${{ steps.source.outputs.R2V }}
|
||||
r2ghidra: ${{ steps.source.outputs.R2GHIDRA_VERSION }}
|
||||
qt_mac: ${{ steps.source.outputs.QT_MAC_INSTALL_VERSION }}
|
||||
qt_win: ${{ steps.source.outputs.QT_WIN_INSTALL_VERSION }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
ref: ${{ inputs.release_tag && format('refs/tags/{0}', inputs.release_tag) || github.sha }}
|
||||
persist-credentials: false
|
||||
- name: Resolve build source and dependencies
|
||||
id: source
|
||||
env:
|
||||
RELEASE_TAG: ${{ inputs.release_tag }}
|
||||
shell: bash
|
||||
run: |
|
||||
if [ -n "$RELEASE_TAG" ]; then
|
||||
[[ "$RELEASE_TAG" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]
|
||||
test "$(./configure -qV)" = "$RELEASE_TAG"
|
||||
fi
|
||||
echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
|
||||
python3 - <<'PYTHON'
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
workflow = Path('.github/workflows/ci.yml').read_text()
|
||||
keys = ('R2V', 'R2GHIDRA_VERSION', 'QT_MAC_INSTALL_VERSION', 'QT_WIN_INSTALL_VERSION')
|
||||
with open(os.environ['GITHUB_OUTPUT'], 'a') as output:
|
||||
for key in keys:
|
||||
value = os.environ[key]
|
||||
if os.environ['RELEASE_TAG']:
|
||||
match = re.search(r'^ ' + key + r': ([0-9.*]+)$', workflow, re.M)
|
||||
if not match:
|
||||
raise SystemExit(f'Missing dependency version: {key}')
|
||||
value = match.group(1)
|
||||
print(f'{key}={value}', file=output)
|
||||
PYTHON
|
||||
|
||||
acr-linux-r2git:
|
||||
if: ${{ github.event_name != 'workflow_dispatch' }}
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
submodules: false # 'recursive' 'true' or 'false'
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: actions/setup-python@v7
|
||||
with:
|
||||
python-version: 3.12.x
|
||||
- name: apt dependencies
|
||||
@@ -32,93 +84,164 @@ jobs:
|
||||
run: |
|
||||
./configure --prefix=/usr
|
||||
make -j4
|
||||
- name: command-line checks
|
||||
run: python3 tests/commandline/test_commandline.py build/iaito
|
||||
- name: packaging
|
||||
run: make -j -C dist/debian
|
||||
indent-check:
|
||||
if: ${{ github.event_name != 'workflow_dispatch' }}
|
||||
runs-on: ubuntu-24.04
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
python-version: 3.12.x
|
||||
- name: Install clang-format 18
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install clang-format-18
|
||||
sudo apt-get install -y libgraphviz-dev mesa-common-dev ninja-build libqt5svg5-dev qttools5-dev qttools5-dev-tools qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools
|
||||
- name: install r2
|
||||
run: |
|
||||
wget -q https://github.com/radareorg/radare2/releases/download/${{env.R2V}}/radare2_${{env.R2V}}_amd64.deb
|
||||
wget -q https://github.com/radareorg/radare2/releases/download/${{env.R2V}}/radare2-dev_${{env.R2V}}_amd64.deb
|
||||
sudo dpkg -i *.deb
|
||||
- name: configure iaito
|
||||
run: ./configure --prefix=/usr
|
||||
persist-credentials: false
|
||||
- name: install clang-format
|
||||
run: sudo apt-get update && sudo apt-get install -y --no-install-recommends clang-format-20
|
||||
- name: make indent
|
||||
run: make indent && git diff --exit-code
|
||||
run: make indent-host && git diff --exit-code
|
||||
acr-linux:
|
||||
needs: source
|
||||
env:
|
||||
R2V: ${{ needs.source.outputs.r2 }}
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
ref: ${{ needs.source.outputs.sha }}
|
||||
submodules: false # 'recursive' 'true' or 'false'
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: actions/setup-python@v7
|
||||
with:
|
||||
python-version: 3.12.x
|
||||
- name: apt dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libgraphviz-dev mesa-common-dev ninja-build libqt5svg5-dev qttools5-dev qttools5-dev-tools qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools
|
||||
sudo apt-get install -y libgraphviz-dev mesa-common-dev ninja-build qt6-base-dev qt6-declarative-dev libqt6svg6-dev qt6-l10n-tools
|
||||
echo /usr/lib/qt6/bin >> $GITHUB_PATH
|
||||
- name: install r2
|
||||
run: |
|
||||
wget -q https://github.com/radareorg/radare2/releases/download/${{env.R2V}}/radare2_${{env.R2V}}_amd64.deb
|
||||
wget -q https://github.com/radareorg/radare2/releases/download/${{env.R2V}}/radare2-dev_${{env.R2V}}_amd64.deb
|
||||
sudo dpkg -i *.deb
|
||||
- name: configure iaito
|
||||
run: ./configure --prefix=/usr
|
||||
run: ./configure --prefix=/usr --with-qmake6
|
||||
- name: build iaito
|
||||
run: make -j4
|
||||
- name: command-line checks
|
||||
if: ${{ hashFiles('tests/commandline/test_commandline.py') != '' }}
|
||||
run: python3 tests/commandline/test_commandline.py build/iaito
|
||||
- name: packaging
|
||||
run: make -C dist/debian
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: iaito-amd64.deb
|
||||
path: dist/debian/*/*.deb
|
||||
acr-linux-nodebugger:
|
||||
if: ${{ github.event_name != 'workflow_dispatch' }}
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
submodules: false # 'recursive' 'true' or 'false'
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@v7
|
||||
with:
|
||||
python-version: 3.12.x
|
||||
- name: apt dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libgraphviz-dev mesa-common-dev ninja-build qt6-base-dev qt6-declarative-dev libqt6svg6-dev qt6-l10n-tools
|
||||
echo /usr/lib/qt6/bin >> $GITHUB_PATH
|
||||
- name: install r2
|
||||
run: |
|
||||
wget -q https://github.com/radareorg/radare2/releases/download/${{env.R2V}}/radare2_${{env.R2V}}_amd64.deb
|
||||
wget -q https://github.com/radareorg/radare2/releases/download/${{env.R2V}}/radare2-dev_${{env.R2V}}_amd64.deb
|
||||
sudo dpkg -i *.deb
|
||||
- name: configure iaito
|
||||
run: ./configure --prefix=/usr --with-qmake6 --without-debugger
|
||||
- name: build iaito
|
||||
run: make -j4
|
||||
- name: command-line checks
|
||||
run: python3 tests/commandline/test_commandline.py build/iaito
|
||||
acr-macos:
|
||||
needs: source
|
||||
env:
|
||||
R2V: ${{ needs.source.outputs.r2 }}
|
||||
R2GHIDRA_VERSION: ${{ needs.source.outputs.r2ghidra }}
|
||||
QT_MAC_INSTALL_VERSION: ${{ needs.source.outputs.qt_mac }}
|
||||
QT_WIN_INSTALL_VERSION: ${{ needs.source.outputs.qt_win }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- arch: arm64
|
||||
macos: latest
|
||||
runs-on: macos-latest
|
||||
- arch: x64
|
||||
macos: 13
|
||||
runs-on: macos-${{ matrix.macos }}
|
||||
runs-on: macos-15-intel
|
||||
arch_alt: x86_64
|
||||
name: acr-macos-${{ matrix.arch }}
|
||||
runs-on: ${{ matrix.runs-on }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
ref: ${{ needs.source.outputs.sha }}
|
||||
submodules: false # 'recursive' 'true' or 'false'
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: actions/setup-python@v7
|
||||
with:
|
||||
python-version: 3.12.x
|
||||
- name: uname
|
||||
run: uname -a
|
||||
- name: install dependencies
|
||||
run: |
|
||||
brew install qt@5
|
||||
echo $(brew --prefix qt@5)/bin >> $GITHUB_PATH
|
||||
pip3 install meson ninja
|
||||
- name: install r2
|
||||
run: pip3 install meson ninja
|
||||
- name: install Qt
|
||||
uses: jurplel/install-qt-action@v4
|
||||
with:
|
||||
version: ${{env.QT_MAC_INSTALL_VERSION}}
|
||||
- name: download radare components
|
||||
working-directory: dist/macos
|
||||
run: |
|
||||
curl -Lo radare2.pkg "https://github.com/radareorg/radare2/releases/download/${{env.R2V}}/radare2-${{ matrix.arch }}-${{env.R2V}}.pkg"
|
||||
sudo installer -pkg radare2.pkg -target /
|
||||
- name: download extra deps
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: make -C dist/macos extra
|
||||
GH_TOKEN: ${{github.token}}
|
||||
run: |
|
||||
validate_download() {
|
||||
case "$2" in
|
||||
tar.gz) tar -tzf "$1" >/dev/null 2>&1 ;;
|
||||
zip) unzip -tq "$1" >/dev/null 2>&1 ;;
|
||||
*) test -s "$1" ;;
|
||||
esac
|
||||
}
|
||||
download() {
|
||||
local output="$1"
|
||||
local url="$2"
|
||||
local type="$3"
|
||||
local attempt
|
||||
|
||||
for ((attempt = 1; attempt <= 3; attempt++)); do
|
||||
rm -f "${output}.tmp"
|
||||
if curl --fail --location --silent --show-error \
|
||||
--connect-timeout 30 --max-time 300 --output "${output}.tmp" "$url" \
|
||||
&& validate_download "${output}.tmp" "$type"; then
|
||||
mv "${output}.tmp" "$output"
|
||||
return 0
|
||||
fi
|
||||
if [ "$attempt" -lt 3 ]; then
|
||||
echo "Download of ${output} failed (attempt ${attempt}/3); retrying in 10s"
|
||||
sleep 10
|
||||
fi
|
||||
done
|
||||
echo "::error::Unable to download a valid ${output} after 3 attempts"
|
||||
return 1
|
||||
}
|
||||
|
||||
r2ai_version="$(gh api /repos/radareorg/r2ai/releases/latest --jq .tag_name)"
|
||||
download radare2.pkg "https://github.com/radareorg/radare2/releases/download/${{env.R2V}}/radare2-${{matrix.arch}}-${{env.R2V}}.pkg" pkg
|
||||
download r2ai.tar.gz "https://github.com/radareorg/r2ai/archive/refs/tags/${r2ai_version}.tar.gz" tar.gz
|
||||
download r2ghidra.zip "https://github.com/radareorg/r2ghidra/releases/download/${{env.R2GHIDRA_VERSION}}/r2ghidra-${{env.R2GHIDRA_VERSION}}-macos-${{matrix.arch_alt||matrix.arch}}.zip" zip
|
||||
download r2ghidra_sleigh.zip "https://github.com/radareorg/r2ghidra/releases/download/${{env.R2GHIDRA_VERSION}}/r2ghidra_sleigh-${{env.R2GHIDRA_VERSION}}.zip" zip
|
||||
make unpack-comps
|
||||
- name: install r2
|
||||
run: sudo installer -pkg dist/macos/radare2.pkg -target /
|
||||
- name: build iaito
|
||||
run: |
|
||||
./configure
|
||||
@@ -127,77 +250,66 @@ jobs:
|
||||
run: codesign --force --deep -s - dist/macos/disk/iaito.app
|
||||
- name: packaging
|
||||
run: make -C dist/macos zip
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: iaito-macos-${{ matrix.arch }}
|
||||
path: dist/macos/iaito.zip
|
||||
meson:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-22.04]
|
||||
python-version: [3.12.x]
|
||||
system-deps: [true]
|
||||
cc-override: [default]
|
||||
cxx-override: [default]
|
||||
# Prevent one job from pausing the rest
|
||||
fail-fast: false
|
||||
if: ${{ github.event_name != 'workflow_dispatch' }}
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
submodules: false # 'recursive' 'true' or 'false'
|
||||
persist-credentials: false
|
||||
- name: apt dependencies
|
||||
if: contains(matrix.os, 'ubuntu')
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libgraphviz-dev mesa-common-dev ninja-build libqt5svg5-dev qttools5-dev qttools5-dev-tools qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools
|
||||
- name: install r2
|
||||
run: git clone --depth=1 https://github.com/radareorg/radare2 r2git && CFLAGS=-O0 r2git/sys/install.sh
|
||||
- uses: actions/setup-python@v5
|
||||
run: |
|
||||
wget -q https://github.com/radareorg/radare2/releases/download/${{env.R2V}}/radare2_${{env.R2V}}_amd64.deb
|
||||
wget -q https://github.com/radareorg/radare2/releases/download/${{env.R2V}}/radare2-dev_${{env.R2V}}_amd64.deb
|
||||
sudo dpkg -i *.deb
|
||||
- uses: actions/setup-python@v7
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: install dependencies
|
||||
if: contains(matrix.os, 'macos')
|
||||
run: |
|
||||
brew install qt@5
|
||||
echo $(brew --prefix qt@5)/bin >> $GITHUB_PATH
|
||||
pip3 install meson ninja
|
||||
- name: macos build
|
||||
if: contains(matrix.os, 'macos')
|
||||
run: |
|
||||
meson setup -Dwith_qt6=true build src
|
||||
ninja -C build -j4
|
||||
python-version: 3.12.x
|
||||
- name: meson+ninja ubuntu
|
||||
if: contains(matrix.os, 'ubuntu')
|
||||
run: |
|
||||
pip install meson
|
||||
meson setup build src
|
||||
ninja -C build -j4
|
||||
w64-meson:
|
||||
needs: source
|
||||
env:
|
||||
R2V: ${{ needs.source.outputs.r2 }}
|
||||
QT_WIN_INSTALL_VERSION: ${{ needs.source.outputs.qt_win }}
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
ref: ${{ needs.source.outputs.sha }}
|
||||
submodules: false
|
||||
persist-credentials: false
|
||||
- name: Preparing msvc toolchain
|
||||
uses: ilammy/msvc-dev-cmd@v1
|
||||
uses: TheMrMilchmann/setup-msvc-dev@v4
|
||||
with:
|
||||
arch: x64
|
||||
- name: install Qt
|
||||
uses: jurplel/install-qt-action@v4
|
||||
with:
|
||||
version: 6.8.*
|
||||
version: ${{env.QT_WIN_INSTALL_VERSION}}
|
||||
- name: install dependencies
|
||||
shell: cmd
|
||||
run: pip install ninja meson
|
||||
run: pip install ninja "meson==1.10.2"
|
||||
- name: install r2 (preconfigure)
|
||||
shell: bash
|
||||
run: |
|
||||
curl -L -o r2.zip https://github.com/radareorg/radare2/releases/download/${{env.R2V}}/radare2-${{env.R2V}}-w64.zip
|
||||
7z x r2.zip
|
||||
mv radare2-${{env.R2V}}-w64 radare2
|
||||
# XXX workaround for r2-6.2.4 headers not being msvc c++ friendly (fixed in r2 master)
|
||||
sed -i 's/(RStrs) { NULL, NULL }/r_strs_new (NULL, NULL)/' radare2/include/libr/r_cmd.h
|
||||
echo "%CD%\radare2\bin" >> $GITHUB_PATH
|
||||
- name: configure
|
||||
shell: cmd
|
||||
@@ -210,46 +322,59 @@ jobs:
|
||||
- name: dist
|
||||
shell: cmd
|
||||
run: 7z a iaito.zip iaito
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: iaito-w64.exe
|
||||
path: iaito\iaito.exe
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: iaito-w64.zip
|
||||
path: iaito.zip
|
||||
|
||||
check_release:
|
||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
||||
if: ${{ github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && github.ref == 'refs/heads/master') }}
|
||||
outputs:
|
||||
is_release: ${{ steps.release.outputs.is }}
|
||||
tag_name: ${{ steps.release.outputs.tag }}
|
||||
needs:
|
||||
- source
|
||||
- acr-linux
|
||||
- acr-macos
|
||||
- w64-meson
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
ref: ${{ needs.source.outputs.sha }}
|
||||
fetch-depth: 0 # Download all git history and tags
|
||||
- name: Check if is a release
|
||||
run: git describe --exact-match --tags ${{ github.sha }} | awk 'BEGIN{tag="-";r="no"}/^[0-9]+\.[0-9]+/{tag=$0;r="yes"};END{print "is="r;print "tag="tag}' >> ${GITHUB_OUTPUT}
|
||||
run: git describe --exact-match --tags HEAD | awk 'BEGIN{tag="-";r="no"}/^[0-9]+\.[0-9]+/{tag=$0;r="yes"};END{print "is="r;print "tag="tag}' >> ${GITHUB_OUTPUT}
|
||||
id: release
|
||||
|
||||
macos-sign:
|
||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && needs.check_release.outputs.is_release == 'yes' }}
|
||||
if: ${{ needs.check_release.outputs.is_release == 'yes' }}
|
||||
needs:
|
||||
- check_release
|
||||
runs-on: macos-latest
|
||||
environment: release
|
||||
# Environment secrets for signing:
|
||||
# APPLE_DEVELOPER_CERTIFICATE_P12_BASE64:
|
||||
# Base64 of the p12 export (cert+private key), the certificate must be named
|
||||
# "Developer ID Application: DEVELOPER_NAME (TEAM_ID)"
|
||||
# APPLE_DEVELOPER_CERTIFICATE_PASSWORD: Random password used to encrypt the exported P12
|
||||
# Environment secrets for notarization:
|
||||
# APPLE_DEVELOPER_ID: email of the developer
|
||||
# APPLE_TEAM_ID: Team ID (can be extracted from the certificate name too)
|
||||
# APPLE_APP_SPECIFIC_PASSWORD: https://support.apple.com/en-us/102654
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
ref: refs/tags/${{ needs.check_release.outputs.tag_name }}
|
||||
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v5
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
pattern: iaito-macos-*
|
||||
path: dist/artifacts
|
||||
@@ -320,34 +445,45 @@ jobs:
|
||||
xcrun stapler staple "iaito-${arch}.dmg"
|
||||
done
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: iaito-arm64.dmg
|
||||
path: iaito-arm64.dmg
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: iaito-x64.dmg
|
||||
path: iaito-x64.dmg
|
||||
|
||||
- name: Remove keychain
|
||||
if: always()
|
||||
run: security delete-keychain release.keychain
|
||||
|
||||
release:
|
||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && needs.check_release.outputs.is_release == 'yes' }}
|
||||
permissions:
|
||||
contents: write
|
||||
if: ${{ needs.check_release.outputs.is_release == 'yes' }}
|
||||
needs:
|
||||
- macos-sign
|
||||
- check_release
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
ref: refs/tags/${{ needs.check_release.outputs.tag_name }}
|
||||
fetch-depth: 0 # Download all git history and tags
|
||||
- name: Extract version
|
||||
run: echo "string=`./configure -qV`" >> $GITHUB_OUTPUT
|
||||
id: version
|
||||
- name: Prepare release notes
|
||||
run: ./scripts/release-notes.sh | tee ./RELEASE_NOTES.md
|
||||
env:
|
||||
RELEASE_TAG: ${{ needs.check_release.outputs.tag_name }}
|
||||
run: |
|
||||
previous=$(git describe --tags --abbrev=0 "${RELEASE_TAG}^")
|
||||
./scripts/release-notes.sh "$previous" "$RELEASE_TAG" | tee ./RELEASE_NOTES.md
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v5
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
path: dist/artifacts
|
||||
- name: Display structure of downloaded files
|
||||
@@ -361,10 +497,8 @@ jobs:
|
||||
tag_name: ${{ needs.check_release.outputs.tag_name }}
|
||||
release_name: ${{ steps.version.outputs.string }}
|
||||
body_path: ./RELEASE_NOTES.md
|
||||
draft: false
|
||||
draft: true
|
||||
prerelease: false
|
||||
- name: Prepare release notes
|
||||
run: ./scripts/release-notes.sh | tee ./RELEASE_NOTES.md
|
||||
- name: Upload Linux asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
@@ -410,3 +544,10 @@ jobs:
|
||||
asset_path: dist/artifacts/iaito-w64.exe/iaito.exe
|
||||
asset_name: iaito-${{ steps.version.outputs.string }}.exe
|
||||
asset_content_type: application/zip
|
||||
- name: Publish complete release
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
RELEASE_ID: ${{ steps.create_release.outputs.id }}
|
||||
run: |
|
||||
gh api --method PATCH "repos/${GITHUB_REPOSITORY}/releases/${RELEASE_ID}" \
|
||||
-F draft=false -f make_latest=legacy --jq .html_url
|
||||
@@ -13,11 +13,11 @@ name: Semgrep
|
||||
jobs:
|
||||
semgrep:
|
||||
name: Scan
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
env:
|
||||
SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}
|
||||
container:
|
||||
image: returntocorp/semgrep
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v7
|
||||
- run: semgrep ci
|
||||
@@ -1,5 +1,10 @@
|
||||
# Binaries
|
||||
AStyle*
|
||||
./config.mk
|
||||
config.mk
|
||||
|
||||
# Generated configuration header (qmake / Meson)
|
||||
IaitoConfig.h
|
||||
|
||||
# OSX files
|
||||
*.DS_Store
|
||||
@@ -20,6 +25,7 @@ AStyle*
|
||||
# Qt files
|
||||
*.qmake.cache
|
||||
*.qmake.stash
|
||||
src/Makefile
|
||||
*.pro.user
|
||||
*.pro.user.*
|
||||
*.qbs.user
|
||||
@@ -100,3 +106,4 @@ src/translations
|
||||
/dist/macos/extra
|
||||
/dist/macos/*.dmg
|
||||
/dist/macos/*.zip
|
||||
/dist/macos/*.tar.gz
|
||||
@@ -0,0 +1,72 @@
|
||||
# Agentic Coding Guidelines for the iaito project
|
||||
|
||||
## Overview
|
||||
|
||||
iaito is the official Qt-based GUI for radare2. It uses qmake for building and requires radare2 and Qt5/6.
|
||||
|
||||
## Locations
|
||||
|
||||
- Source code is in `./src/` (widgets, dialogs, menus, common, core, plugins)
|
||||
- Qt project file: `./src/Iaito.pro`
|
||||
- Plugin interface: `./src/plugins/IaitoPlugin.h`
|
||||
- Sample plugins: `./src/plugins/sample-cpp/` and `./src/plugins/sample-python/`
|
||||
- UI forms: `./src/dialogs/*.ui` and `./src/widgets/*.ui`
|
||||
- Resources: `./src/resources.qrc`, `./src/themes/`
|
||||
|
||||
## Formatting Style
|
||||
|
||||
- Follow C++ / Qt Creator coding style (see `.clang-format`)
|
||||
- Indent with **4 spaces** (no tabs)
|
||||
- Column limit: 100 characters
|
||||
- Use `make fmt` to format the whole project source
|
||||
|
||||
## Coding Rules
|
||||
|
||||
- C++20 standard
|
||||
- Code must be portable across windows, macos and linux
|
||||
- Use Qt signals/slots for communication between components
|
||||
- Widgets inherit from `IaitoDockWidget` or `AddressableDockWidget`
|
||||
- Access radare2 via the `Core()` singleton (defined in `core/Iaito.h`)
|
||||
- Use `CutterCore::cmd()` and `CutterCore::cmdj()` for r2 commands
|
||||
|
||||
## Actions
|
||||
|
||||
- Configure: `./configure`
|
||||
- Build: `make -j8 > /dev/null`
|
||||
- Never run qmake or compilers directly
|
||||
- Limit to 8 jobs to avoid resource exhaustion
|
||||
- Redirect stdout to /dev/null to reduce noise
|
||||
- Run: `make run`
|
||||
- Clean: `make clean`
|
||||
- Install to home: `make user-install`
|
||||
- Build r2 plugin: `make plugin`
|
||||
|
||||
## Dependencies
|
||||
|
||||
Requires radare2 installed system-wide:
|
||||
```sh
|
||||
git clone https://github.com/radareorg/radare2 && radare2/sys/install.sh
|
||||
```
|
||||
|
||||
Qt5 packages (Debian/Ubuntu):
|
||||
```sh
|
||||
sudo apt install qttools5-dev qtbase5-dev libqt5svg5-dev
|
||||
```
|
||||
|
||||
## Adding Widgets
|
||||
|
||||
1. Create `.h`, `.cpp`, and `.ui` files in `src/widgets/`
|
||||
2. Add to `SOURCES`, `HEADERS`, `FORMS` in `src/Iaito.pro`
|
||||
3. Register in `core/MainWindow.cpp`
|
||||
|
||||
## Adding Plugins
|
||||
|
||||
- C++ plugins: see `src/plugins/sample-cpp/`
|
||||
- Python plugins: see `src/plugins/sample-python/`
|
||||
- Implement `IaitoPlugin` interface from `plugins/IaitoPlugin.h`
|
||||
|
||||
## Debugging
|
||||
|
||||
- Run with: `R2_DEBUG=1 make run`
|
||||
- Debug with gdb: `make gdb`
|
||||
- Debug with lldb: `make lldb`
|
||||
@@ -1,4 +1,4 @@
|
||||
include config.mk
|
||||
-include config.mk
|
||||
|
||||
ifeq ($(shell uname),Darwin)
|
||||
BIN=build/iaito.app/Contents/MacOS/iaito
|
||||
@@ -6,20 +6,29 @@ else
|
||||
BIN=build/iaito
|
||||
endif
|
||||
|
||||
#QMAKE_FLAGS+=IAITO_ENABLE_PYTHON=false
|
||||
QMAKE_FLAGS+=-config release
|
||||
|
||||
#install_name_tool -change /path/to/Qt/lib/QtCore.framework/Versions/4.0/QtCore
|
||||
# @executable_path/../Frameworks/QtCore.framework/Versions/4.0/QtCore
|
||||
# plugandpaint.app/Contents/plugins/libpnp_basictools.dylib
|
||||
|
||||
ifeq ($(WANT_PYTHON),1)
|
||||
QMAKE_FLAGS+=IAITO_ENABLE_PYTHON=true
|
||||
endif
|
||||
ifeq ($(WANT_PYTHON_BINDINGS),1)
|
||||
QMAKE_FLAGS+=IAITO_ENABLE_PYTHON_BINDINGS=true
|
||||
endif
|
||||
ifeq ($(WANT_DEBUGGER),0)
|
||||
QMAKE_FLAGS+=IAITO_ENABLE_DEBUGGER=false
|
||||
else
|
||||
QMAKE_FLAGS+=IAITO_ENABLE_DEBUGGER=true
|
||||
endif
|
||||
ifeq ($(WANT_CRASH_REPORTS),1)
|
||||
QMAKE_FLAGS+=IAITO_ENABLE_CRASH_REPORTS=true
|
||||
endif
|
||||
QMAKE_FLAGS+=PREFIX=$(PREFIX)
|
||||
INDENT_IMAGE_NAME ?= clang-format-image
|
||||
INDENT_IMAGE_TAG ?= llvm20
|
||||
INDENT_IMAGE := $(INDENT_IMAGE_NAME):$(INDENT_IMAGE_TAG)
|
||||
INDENT_FILES := $(shell find src \( -name '*.cpp' -o -name '*.h' \))
|
||||
CLANG_FORMAT ?= clang-format-20
|
||||
|
||||
all: iaito
|
||||
|
||||
@@ -47,6 +56,7 @@ asan:
|
||||
|
||||
clean:
|
||||
rm -f src/ui_*.h
|
||||
rm -f src/moc_*
|
||||
rm -rf build
|
||||
|
||||
dist:
|
||||
@@ -60,6 +70,8 @@ mrproper: clean
|
||||
git clean -xdf
|
||||
|
||||
.PHONY: install run user-install dist macos clean mrproper install-translations
|
||||
.PHONY: dockindent dockindent-image indent indent-host
|
||||
.PHONY: install-deeplink uninstall-deeplink
|
||||
|
||||
# force qt5 build when QtCreator is installed in user's home
|
||||
ifeq ($(shell test -x ~/Qt/5.*/clang_64/bin/qmake || echo err),)
|
||||
@@ -86,6 +98,8 @@ ifeq ($(shell uname),Darwin)
|
||||
ln -fs '/Applications/iaito.app/Contents/MacOS/iaito' $(DESTDIR)/$(PREFIX)'/bin/iaito'
|
||||
else
|
||||
$(MAKE) -C build install INSTALL_ROOT=$(DESTDIR)
|
||||
# Refresh the desktop DB so the .desktop MimeType registers iaito://
|
||||
-update-desktop-database "$(DESTDIR)$(PREFIX)/share/applications" 2>/dev/null
|
||||
endif
|
||||
$(MAKE) install-man
|
||||
|
||||
@@ -98,6 +112,7 @@ else
|
||||
rm -rf "$(DESTDIR)/$(PREFIX)/bin/iaito"
|
||||
endif
|
||||
rm -f "${DESTDIR}$(MANDIR)/man1/iaito.1"
|
||||
-$(MAKE) uninstall-deeplink
|
||||
|
||||
user-install: build
|
||||
ifeq ($(shell uname),Darwin)
|
||||
@@ -108,13 +123,36 @@ else
|
||||
$(MAKE) -C build install INSTALL_ROOT=/ PREFIX=${HOME}/.local
|
||||
endif
|
||||
$(MAKE) install-man DESTDIR=/ PREFIX=${HOME}/.local MANDIR=${HOME}/.local/share/man
|
||||
$(MAKE) install-deeplink
|
||||
|
||||
user-uninstall:
|
||||
$(MAKE) uninstall DESTDIR=/ PREFIX=${HOME}/.local MANDIR=${HOME}/.local/share/man
|
||||
|
||||
ifneq ($(shell uname),Darwin)
|
||||
run: install-desktop-user
|
||||
endif
|
||||
|
||||
run:
|
||||
rarun2 libpath=$(shell r2 -H R2_LIBDIR) program=$(BIN)
|
||||
|
||||
install-desktop-user:
|
||||
@mkdir -p $(HOME)/.local/share/applications
|
||||
@mkdir -p $(HOME)/.local/share/icons/hicolor/scalable/apps
|
||||
@cp -f src/org.radare.iaito.desktop $(HOME)/.local/share/applications/org.radare.iaito.desktop
|
||||
@cp -f src/img/org.radare.iaito.svg $(HOME)/.local/share/icons/hicolor/scalable/apps/org.radare.iaito.svg
|
||||
@$(MAKE) install-deeplink
|
||||
|
||||
# Register/unregister the iaito:// deep link URI scheme handler
|
||||
install-deeplink:
|
||||
ifeq ($(shell uname),Darwin)
|
||||
sh scripts/deeplink/register.sh "$(DESTDIR)/Applications/iaito.app"
|
||||
else
|
||||
sh scripts/deeplink/register.sh
|
||||
endif
|
||||
|
||||
uninstall-deeplink:
|
||||
sh scripts/deeplink/unregister.sh
|
||||
|
||||
gdb:
|
||||
gdb --args $(BIN)
|
||||
|
||||
@@ -131,13 +169,16 @@ user-install-translations: src/translations/README.md
|
||||
$(MAKE) -C src/translations user-install
|
||||
|
||||
dockindent:
|
||||
docker images | grep clang-format-image || make -C scripts/indent
|
||||
docker run --rm -v "$$PWD:/project" clang-format-image -i \
|
||||
$(shell find src -name '*.cpp' -o -name '*.h')
|
||||
$(MAKE) dockindent-image
|
||||
docker run --rm -v "$$PWD:/project" $(INDENT_IMAGE) -i $(INDENT_FILES)
|
||||
|
||||
indent:
|
||||
ifeq ($(clang-format --version | grep 'version 18'),)
|
||||
dockindent-image:
|
||||
docker image inspect $(INDENT_IMAGE) >/dev/null 2>&1 || \
|
||||
$(MAKE) -C scripts/indent IMAGE_NAME=$(INDENT_IMAGE_NAME) IMAGE_TAG=$(INDENT_IMAGE_TAG)
|
||||
|
||||
fmt format indent:
|
||||
$(MAKE) dockindent
|
||||
else
|
||||
clang-format -i $(shell find src -name '*.cpp' -o -name '*.h')
|
||||
endif
|
||||
|
||||
indent-host:
|
||||
$(CLANG_FORMAT) --version | grep 'version 20.'
|
||||
$(CLANG_FORMAT) -i $(INDENT_FILES)
|
||||
@@ -19,7 +19,7 @@ iaito is the official graphical interface for radare2, a libre reverse engineeri
|
||||
|
||||
Get help, updates, meet the community or discuss about development in these channels:
|
||||
|
||||
- **Discord:** [https://discord.gg/6RwDEJFr](https://discord.gg/6RwDEJFr)
|
||||
- **Discord:** [https://radare.org/discord](https://radare.org/discord)
|
||||
- **Telegram:** https://t.me/radare
|
||||
- **Mastodon:** [@radareorg](https://infosec.exchange/@radareorg)
|
||||
- **Translations:** [crowdin](https://crowdin.com/project/iaito)!
|
||||
@@ -83,6 +83,9 @@ make
|
||||
make run
|
||||
```
|
||||
|
||||
On macOS, `make run` launches the app from `build/iaito.app` without installing it.
|
||||
Registering the `iaito://` URI handler is optional; see [deep links](doc/deeplinks.md#macos).
|
||||
|
||||
To install the app in your home:
|
||||
|
||||
```sh
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
PREFIX=@PREFIX@
|
||||
QMAKE=@QMAKE_BIN@
|
||||
|
||||
WANT_PYTHON=@WANT_PYTHON@
|
||||
WANT_PYTHON_BINDINGS=@WANT_PYTHON_BINDINGS@
|
||||
WANT_CRASH_REPORTS=@WANT_CRASH_REPORTS@
|
||||
WANT_DEBUGGER=@WANT_DEBUGGER@
|
||||
|
||||
DESTDIR?=
|
||||
MANDIR=@MANDIR@
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/bin/sh
|
||||
# This script was automatically generated by ACR v2.2.4
|
||||
# This script was automatically generated by ACR v2.2.8
|
||||
# @author: pancake <nopcode.org>
|
||||
# @url: http://www.nopcode.org
|
||||
# @repo: git clone https://github.com/radare/acr
|
||||
@@ -19,6 +19,8 @@ control_c() {
|
||||
}
|
||||
trap control_c 2
|
||||
WANT_QMAKE6=0
|
||||
WANT_CRASH_REPORTS=0
|
||||
WANT_DEBUGGER=1
|
||||
split_host() {
|
||||
S="$"
|
||||
while : ; do
|
||||
@@ -127,12 +129,12 @@ done
|
||||
: ${INSTALL_PROGRAM_STRIP:=${INSTALL} -m 755 -s}
|
||||
: ${INSTALL_MAN:=${INSTALL} -m 444}
|
||||
: ${INSTALL_LIB:=${INSTALL} -m 755 -c}
|
||||
PKGNAME='iaito' ; VERSION='6.0.2' ; VERSION_MAJOR=6; VERSION_MINOR=0; VERSION_PATCH=2; VERSION_NUMBER=60002; CONTACT_MAIL="[email protected]" ; CONTACT_NAME="pancake" ; CONTACT="pancake <[email protected]>" ;
|
||||
PKGNAME='iaito' ; VERSION='6.2.6' ; VERSION_MAJOR=6; VERSION_MINOR=2; VERSION_PATCH=6; VERSION_NUMBER=60206; CONTACT_MAIL="[email protected]" ; CONTACT_NAME="pancake" ; CONTACT="pancake <[email protected]>" ;
|
||||
}
|
||||
|
||||
show_usage() {
|
||||
cat <<EOF2
|
||||
'configure' configures iaito-6.0.2 to adapt to many kinds of systems.
|
||||
'configure' configures iaito-6.2.6 to adapt to many kinds of systems.
|
||||
|
||||
Usage: ./configure [OPTION]... [VAR=VALUE]...
|
||||
|
||||
@@ -177,6 +179,8 @@ EOF2
|
||||
printf "
|
||||
Optional Features:
|
||||
--with-qmake6 ignore qmake in path when qmake6 is found
|
||||
--with-crash-reports handle crashes nicely
|
||||
--without-debugger native support for debugging
|
||||
"
|
||||
printf "
|
||||
Some influential environment variables:
|
||||
@@ -208,10 +212,10 @@ ocho() {
|
||||
|
||||
show_version() {
|
||||
if [ "$QUIET" = 1 ]; then
|
||||
echo "6.0.2"
|
||||
echo "6.2.6"
|
||||
exit 0
|
||||
fi
|
||||
echo "iaito-6.0.2 configuration script done with acr v2.2.4.
|
||||
echo "iaito-6.2.6 configuration script done with acr v2.2.8.
|
||||
The 'Free Software Foundation' message is only for autodetection.
|
||||
Originally written by pancake <nopcode.org>."
|
||||
exit 0
|
||||
@@ -240,12 +244,12 @@ case $flag in
|
||||
show_version ; ;;
|
||||
-r|--r|--report)
|
||||
echo "PKGNAME: iaito"
|
||||
echo "VERSION: 6.0.2"
|
||||
echo "VERSION: 6.2.6"
|
||||
echo "AUTHOR: pancake"
|
||||
echo "EMAIL: [email protected]"
|
||||
echo "LANGS: c++"
|
||||
echo "PKGCONFIG: r_core"
|
||||
echo "FLAGS: --with-qmake6"
|
||||
echo "FLAGS: --with-qmake6 --with-crash-reports --without-debugger"
|
||||
exit 0
|
||||
;;
|
||||
--cache-file)
|
||||
@@ -298,6 +302,8 @@ echo "FLAGS: --with-qmake6"
|
||||
MANDIR="$value"; ;;
|
||||
|
||||
"--with-qmake6") WANT_QMAKE6="1"; ;;
|
||||
"--with-crash-reports") WANT_CRASH_REPORTS="1"; ;;
|
||||
"--without-debugger") WANT_DEBUGGER="0"; ;;
|
||||
*) if [ "$value" ]; then eval "`echo $flag2=$value`" ;
|
||||
else echo ; echo "WARNING: Unknown flag '$flag'." >&2 ; echo ; fi ;;
|
||||
esac
|
||||
@@ -315,7 +321,7 @@ parse_options "$1"
|
||||
shift
|
||||
done
|
||||
|
||||
ENVWORDS="MANDIR DESCRIPTION INFODIR LIBDIR INCLUDEDIR LOCALSTATEDIR ETCDIR SYSCONFDIR DATADIR DOCDIR LIBEXECDIR SBINDIR BINDIR EPREFIX PREFIX SPREFIX TARGET HOST BUILD INSTALL INSTALL_LIB INSTALL_MAN INSTALL_PROGRAM INSTALL_PROGRAM_STRIP INSTALL_DIR INSTALL_SCRIPT INSTALL_DATA HOST_OS HOST_CPU BUILD_OS BUILD_CPU TARGET_OS TARGET_CPU VERSION VERSION_MAJOR VERSION_MINOR VERSION_PATCH VERSION_NUMBER PKGCFG_LIBDIR PKGCFG_INCDIR PKGNAME VPATH CONTACT CONTACT_NAME CONTACT_MAIL CXX CXXFLAGS LDFLAGS HAVE_LANG_CXX CPP PKGCONFIG HAVE_R2 R2 R2_CFLAGS R2_LDFLAGS HAVE_PKGCFG_R_CORE HAVE_QMAKE QMAKE HAVE_QMAKE6 QMAKE6 WANT_QMAKE6 QMAKE_BIN WANT_PYTHON WANT_PYTHON_BINDINGS WANT_CRASH_REPORTS"
|
||||
ENVWORDS="MANDIR DESCRIPTION INFODIR LIBDIR INCLUDEDIR LOCALSTATEDIR ETCDIR SYSCONFDIR DATADIR DOCDIR LIBEXECDIR SBINDIR BINDIR EPREFIX PREFIX SPREFIX TARGET HOST BUILD INSTALL INSTALL_LIB INSTALL_MAN INSTALL_PROGRAM INSTALL_PROGRAM_STRIP INSTALL_DIR INSTALL_SCRIPT INSTALL_DATA HOST_OS HOST_CPU BUILD_OS BUILD_CPU TARGET_OS TARGET_CPU VERSION VERSION_MAJOR VERSION_MINOR VERSION_PATCH VERSION_NUMBER PKGCFG_LIBDIR PKGCFG_INCDIR PKGNAME VPATH CONTACT CONTACT_NAME CONTACT_MAIL CXX CXXFLAGS LDFLAGS HAVE_LANG_CXX CPP PKGCONFIG HAVE_R2 R2 R2_CFLAGS R2_LDFLAGS HAVE_PKGCFG_R_CORE HAVE_QMAKE QMAKE HAVE_QMAKE6 QMAKE6 WANT_QMAKE6 QMAKE_BIN WANT_CRASH_REPORTS WANT_DEBUGGER"
|
||||
|
||||
create_environ
|
||||
|
||||
@@ -492,8 +498,6 @@ if [ "$HAVE_QMAKE" = "1" ]; then
|
||||
QMAKE_BIN="${QMAKE}"; fi; fi
|
||||
if [ "$QMAKE_BIN" = """" ]; then
|
||||
case "$undefined_variable" in 0|"") echo ; echo "ERROR: Missing qmake" >&2 ; echo ; exit 1 ;; esac; fi
|
||||
WANT_PYTHON="0"
|
||||
WANT_PYTHON_BINDINGS="0"
|
||||
WANT_CRASH_REPORTS="0"
|
||||
SEDFLAGS=" -e '"
|
||||
COUNT=0
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
PKGNAME iaito
|
||||
VERSION 6.0.2
|
||||
VERSION 6.2.6
|
||||
CONTACT pancake ; [email protected]
|
||||
|
||||
LANG_CXX!
|
||||
@@ -30,14 +30,10 @@ IFEQ QMAKE_BIN "" ; {
|
||||
DIENOW Missing qmake ;
|
||||
}
|
||||
|
||||
WANT_PYTHON = 0 ;
|
||||
WANT_PYTHON_BINDINGS = 0 ;
|
||||
WANT_CRASH_REPORTS = 0 ;
|
||||
((
|
||||
ARG_WITH WANT_PYTHON python build with support for Python ;
|
||||
ARG_WITH WANT_PYTHON_BINDINGS python-bindings build with Python bindings ;
|
||||
|
||||
ARG_WITH WANT_CRASH_REPORTS crash-reports handle crashes nicely ;
|
||||
))
|
||||
ARG_WITHOUT WANT_DEBUGGER debugger native support for debugging ;
|
||||
|
||||
SUBDIRS config.mk ;
|
||||
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
|
||||
set(EMBED_PYTHON_SH "@EMBED_PYTHON_SH@")
|
||||
set(PYTHON_FRAMEWORK_DIR "@PYTHON_FRAMEWORK_DIR@")
|
||||
set(PYSIDE_PREFIX "@PYSIDE_PREFIX@")
|
||||
|
||||
get_filename_component(BUNDLE_PATH "${CMAKE_INSTALL_PREFIX}/../.." ABSOLUTE)
|
||||
|
||||
set(ENV{PKG_CONFIG_PATH} "${PYSIDE_PREFIX}/lib/pkgconfig:$ENV{PKG_CONFIG_PATH}")
|
||||
|
||||
# TODO: instead of calling a shellscript, implement the whole thing in cmake
|
||||
execute_process(COMMAND
|
||||
"${EMBED_PYTHON_SH}"
|
||||
"${PYTHON_FRAMEWORK_DIR}"
|
||||
"${BUNDLE_PATH}"
|
||||
"${BUNDLE_PATH}/Contents/MacOS/Iaito"
|
||||
RESULT_VARIABLE SCRIPT_RESULT)
|
||||
if(SCRIPT_RESULT)
|
||||
message(FATAL_ERROR "Failed to bundle python")
|
||||
endif()
|
||||
|
||||
@@ -6,7 +6,6 @@ set(ADJUST_RIZIN_LIBS "@ADJUST_RIZIN_LIBS@")
|
||||
set(IAITO_ENABLE_CRASH_REPORTS "@IAITO_ENABLE_CRASH_REPORTS@")
|
||||
set(Breakpad_LINK_LIBRARIES "@Breakpad_LINK_LIBRARIES@")
|
||||
set(IAITO_PACKAGE_DEPENDENCIES "@IAITO_PACKAGE_DEPENDENCIES@")
|
||||
set(IAITO_ENABLE_PYTHON "@IAITO_ENABLE_PYTHON@")
|
||||
|
||||
macro(run_or_die)
|
||||
execute_process(${ARGV} RESULT_VARIABLE PROC_RESULT)
|
||||
@@ -65,13 +64,7 @@ run_or_die(COMMAND install_name_tool
|
||||
set(MACDEPLOYQT_COMMAND "${MACDEPLOYQT_PATH}" "${BUNDLE_PATH}" "-verbose=2" "-libpath=${CMAKE_INSTALL_PREFIX}/lib")
|
||||
message("Running macdeployqt \"${MACDEPLOYQT_COMMAND}\"")
|
||||
run_or_die(COMMAND ${MACDEPLOYQT_COMMAND}) # First run
|
||||
if (IAITO_PACKAGE_DEPENDENCIES AND IAITO_ENABLE_PYTHON)
|
||||
file(REAL_PATH "${FRAMEWORK_DIR}/Python.framework/Python" _python_lib_path)
|
||||
message("Python lib ${_python_lib_path}")
|
||||
list(APPEND MACDEPLOYQT_COMMAND "-executable=${_python_lib_path}")
|
||||
endif()
|
||||
# Qt plugins are not getting deployed on first macdeployqt run. Runing twice helps.
|
||||
# If python is added as additional executable in first run it also breaks plugin copying.
|
||||
# Qt plugins are not getting deployed on first macdeployqt run. Running twice helps.
|
||||
run_or_die(COMMAND ${MACDEPLOYQT_COMMAND}) # Second run
|
||||
|
||||
# Clean up the mess that macdeployqt made (duplicate rz libs, we only want the ones in the prefix)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
NAME=iaito
|
||||
SECTION=editors
|
||||
PRIORITY=optional
|
||||
DEPENDS=$(shell ../getpkgversions.sh radare2 libqt5core5a libqt5gui5 libqt5widgets5 libqt5network5 libqt5svg5)
|
||||
DEPENDS=$(shell ../getpkgversions.sh radare2), libqt6core6 (>= 6.2.4), libqt6gui6 (>= 6.2.4), libqt6widgets6 (>= 6.2.4), libqt6network6 (>= 6.2.4), libqt6svg6 (>= 6.2.4), libqt6svgwidgets6 (>= 6.2.4), qt6-qpa-plugins (>= 6.2.4)
|
||||
MAINTAINER=pancake <[email protected]>
|
||||
ARCH?=amd64
|
||||
VERSION=$(shell ../../../configure -qV)
|
||||
|
||||
@@ -1,16 +1,29 @@
|
||||
.PHONY: all app dmg zip extra clean mrproper
|
||||
.PHONY: all app dmg zip unpack-comps clean mrproper
|
||||
|
||||
all: clean app dmg
|
||||
app: disk/iaito.app
|
||||
dmg: iaito.dmg
|
||||
zip: iaito.zip
|
||||
extra: extra/r2ai
|
||||
unpack-comps: radare2-unpkg extra/r2ai extra/r2ghidra extra/r2ghidra_sleigh
|
||||
|
||||
disk/iaito.app: ../../build/iaito.app radare2-unpkg extra/r2ai
|
||||
disk/iaito.app: ../../build/iaito.app unpack-comps
|
||||
mkdir disk
|
||||
# Copy base bundle app
|
||||
cp -a ../../build/iaito.app disk/
|
||||
# Copy r2 binary plugins
|
||||
mkdir -p disk/iaito.app/Contents/PlugIns/radare2
|
||||
install -p -m 0755 \
|
||||
extra/r2ghidra/core_ghidra.dylib \
|
||||
disk/iaito.app/Contents/PlugIns/radare2/
|
||||
# Copy r2 pkg files and update linking path for all binary files
|
||||
scripts/embed-radare2.sh radare2-unpkg disk/iaito.app
|
||||
cp -a extra/r2ai/decai/decai.r2.js disk/iaito.app/Contents/Resources/radare2/lib/radare2/last/
|
||||
# Copy r2 non-binary plugins
|
||||
install -p -m 0644 \
|
||||
extra/r2ai/decai/decai.r2.js \
|
||||
disk/iaito.app/Contents/Resources/radare2/lib/radare2/last/
|
||||
cp -a extra/r2ghidra_sleigh \
|
||||
disk/iaito.app/Contents/Resources/radare2/share/
|
||||
# Copy QT frameworks
|
||||
macdeployqt disk/iaito.app -verbose=2
|
||||
|
||||
iaito.dmg: disk/iaito.app
|
||||
@@ -24,15 +37,44 @@ iaito.zip:
|
||||
|
||||
radare2-unpkg: radare2.pkg
|
||||
pkgutil --expand-full $< $@
|
||||
touch $@
|
||||
|
||||
radare2.pkg:
|
||||
echo "Download first radare2.pkg from https://github.com/radareorg/radare2/releases"
|
||||
@false
|
||||
|
||||
extra/r2ai:
|
||||
r2ai.tar.gz:
|
||||
echo "Download first r2ai.tar.gz (source) from https://github.com/radareorg/r2ai/releases"
|
||||
@false
|
||||
|
||||
extra/r2ai: r2ai.tar.gz
|
||||
mkdir -p extra
|
||||
curl -L "$(shell gh api /repos/radareorg/r2ai/releases/latest --jq .tarball_url)" | tar -xzC extra/
|
||||
cd extra && mv radareorg-r2ai-* r2ai
|
||||
rm -rf extra/r2ai extra/r2ai-* extra/radareorg-r2ai-*
|
||||
tar -f $< -xzC extra/
|
||||
cd extra && mv *r2ai-* r2ai
|
||||
touch $@
|
||||
|
||||
r2ghidra.zip:
|
||||
echo "Download first r2ghidra.zip (binary) from https://github.com/radareorg/r2ghidra/releases"
|
||||
@false
|
||||
|
||||
extra/r2ghidra: r2ghidra.zip
|
||||
mkdir -p extra
|
||||
rm -rf extra/r2ghidra extra/r2ghidra-*-macos-*
|
||||
unzip $< -d extra/
|
||||
cd extra && mv r2ghidra-*-macos-* r2ghidra
|
||||
touch $@
|
||||
|
||||
r2ghidra_sleigh.zip:
|
||||
echo "Download first r2ghidra_sleigh.zip from https://github.com/radareorg/r2ghidra/releases"
|
||||
@false
|
||||
|
||||
extra/r2ghidra_sleigh: r2ghidra_sleigh.zip
|
||||
mkdir -p extra
|
||||
rm -rf extra/r2ghidra_sleigh*
|
||||
unzip $< -d extra/
|
||||
cd extra && mv r2ghidra_sleigh-* r2ghidra_sleigh
|
||||
touch $@
|
||||
|
||||
../../build/iaito.app:
|
||||
echo "Building iaito..."
|
||||
@@ -42,4 +84,4 @@ clean:
|
||||
rm -rf radare2-unpkg disk iaito.dmg
|
||||
|
||||
mrproper: clean
|
||||
rm -rf radare2.pkg extra
|
||||
rm -rf radare2.pkg r2ai.tar.gz r2ghidra*.zip extra
|
||||
@@ -8,9 +8,15 @@ R2DIR="${R2PKGDIR}/Payload/usr/local"
|
||||
R2V=$(readlink "${R2DIR}/lib/radare2/last")
|
||||
|
||||
fix_binary() {
|
||||
echo "Change library paths for \"$1\"..."
|
||||
ARGS=$(otool -L "$1" | awk '/\/usr\/local\/lib\/libr_/{dst=$1; sub(/\/usr\/local\/lib/,"@executable_path/../Frameworks", dst); print "-change "$1" "dst}')
|
||||
[ -z "$ARGS" ] || install_name_tool $ARGS "$1"
|
||||
FILE=$1
|
||||
PREFIX=$2
|
||||
shift 2
|
||||
echo "Change library paths for \"$FILE\"..."
|
||||
ARGS=$(otool -L "$FILE" | awk 'BEGIN{ORS=" "}/\/usr\/local\/lib\/libr_/{dst=$1; sub(/\/usr\/local\/lib/,"'"$PREFIX"'", dst); print "-change "$1" "dst}')
|
||||
if [ -n "$ARGS" -o -n "$1" ]; then
|
||||
echo " install_name_tool $@ $ARGS \"$FILE\""
|
||||
install_name_tool $@ $ARGS "$FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
mkdir -p \
|
||||
@@ -30,15 +36,18 @@ cp -p "${SCRIPTS}/command.sh" "${APPDIR}/Contents/Resources/radare
|
||||
cp -a "${R2DIR}/lib/radare2/last" "${APPDIR}/Contents/Resources/radare2/lib/radare2/"
|
||||
#cp -a "${R2DIR}/lib/radare2/${R2V}/"*.js "${APPDIR}/Contents/Resources/radare2/lib/radare2/${R2V}/"
|
||||
|
||||
# Move all non-binary files outside Helpers folder
|
||||
mv "${APPDIR}/Contents/Helpers/clang-format-radare2" "${APPDIR}/Contents/Resources/radare2/bin/"
|
||||
|
||||
(
|
||||
cd "${APPDIR}/Contents/MacOS"
|
||||
fix_binary "iaito"
|
||||
fix_binary "iaito" "@rpath"
|
||||
)
|
||||
|
||||
(
|
||||
cd "${APPDIR}/Contents/Helpers"
|
||||
for c in *; do
|
||||
[ -L "$c" ] || fix_binary "$c"
|
||||
[ -L "$c" ] || fix_binary "$c" "@rpath" -add_rpath "@executable_path/../Frameworks"
|
||||
[ "$c" != "radare2" ] && ln -s radare2 "../Resources/radare2/bin/$c"
|
||||
done
|
||||
)
|
||||
@@ -47,7 +56,7 @@ cp -a "${R2DIR}/lib/radare2/last" "${APPDIR}/Contents/Resources/radare
|
||||
LIBS=$(cd "${R2DIR}/lib"; ls *.dylib)
|
||||
cd "${APPDIR}/Contents/Frameworks"
|
||||
for c in $LIBS; do
|
||||
[ -L "$c" ] || fix_binary "$c"
|
||||
[ -L "$c" ] || fix_binary "$c" "@loader_path" -id "@rpath/$c"
|
||||
c2=$c # Resolve upto 2 link levels
|
||||
[ -L "$c2" ] && c2=$(readlink "$c2")
|
||||
[ -L "$c2" ] && c2=$(readlink "$c2")
|
||||
@@ -58,7 +67,9 @@ cp -a "${R2DIR}/lib/radare2/last" "${APPDIR}/Contents/Resources/radare
|
||||
(
|
||||
cd "${APPDIR}/Contents/PlugIns/radare2"
|
||||
for c in *.dylib; do
|
||||
[ -L "$c" ] || fix_binary "$c"
|
||||
[ -L "$c" ] || fix_binary "$c" "@rpath" -add_rpath "@loader_path/../../Frameworks" -id "$c"
|
||||
ln -s "../../../../../PlugIns/radare2/$c" "../../Resources/radare2/lib/radare2/${R2V}/$c"
|
||||
done
|
||||
# Required for plugins rpath when loaded from Resources
|
||||
ln -s "../../../Frameworks" "../../Resources/radare2/lib/Frameworks"
|
||||
)
|
||||
@@ -0,0 +1,50 @@
|
||||
# Data Analysis
|
||||
|
||||
Open **View → Data → Analysis**. The dock is hidden in the default layout and when
|
||||
first added to an older saved layout. Hidden docks do not sample data. **Zoom** is
|
||||
also available under **View → Data**.
|
||||
|
||||
- **Algorithms** enables multiple byte metrics. Hover over a named legend entry
|
||||
for its definition; click it to edit its color or visibility. Clicking the legend
|
||||
does not remove the series.
|
||||
- **Options → Resolution (buckets)** selects 32, 64, 128, 256, 512, 1024, 2048 or
|
||||
4096 buckets. Lower values give coarser graphs and less work. The default is 1024.
|
||||
- **Options** also controls lines, filled lines or vertical bars, separate tracks,
|
||||
greyscale/rainbow/theme colors, custom colors, and structural markers.
|
||||
- Click the graph to seek. Drag to select bytes. Wheel or `+`/`-` zooms;
|
||||
Shift+wheel or the arrow keys pans. `Home` fits all data, and `Esc` clears selection.
|
||||
- The bottom entropy overview shows the complete address span. Drag its window
|
||||
to pan, drag its edges to resize the range, or double-click to fit all data.
|
||||
- **Range** fits the current section, segment, map, block or selection.
|
||||
**Edit range** accepts radare2 expressions and inclusive endpoints.
|
||||
- **Follow** keeps the current seek address visible. Offset synchronization and
|
||||
the global address-selection synchronization setting control integration with
|
||||
disassembly and hexdump widgets.
|
||||
- **Find marker** searches sections, segments, maps, flags (including search hits)
|
||||
and comments. The graph context menu provides comments, flags and “Show in”.
|
||||
Hover details appear after the pointer rests for 650 ms.
|
||||
- Hexdump selections, sections and segments have direct analysis actions.
|
||||
Other addressable widgets can use **Show in → Data Analysis**.
|
||||
|
||||
The graph uses the current radare2 address space: virtual addresses with `io.va`
|
||||
enabled, otherwise physical offsets in the active descriptor. Gaps and failed reads
|
||||
do not contribute invented bytes. Each bucket reads at most 4096 bytes from centered
|
||||
windows in readable spans. Tooltips, the status line and exported CSV report how
|
||||
many bytes were actually read. Zoom in for complete coverage of a region. Entropy
|
||||
is measured per bucket, so very small buckets have a lower attainable entropy.
|
||||
|
||||
Multiple overlaid series use a normalized percentage scale. Entropy's native scale
|
||||
is 0–8 bits/byte, unique bytes 0–256, and mean byte 0–255. A single series or separate
|
||||
tracks uses its native scale. CSV exports normalized values plus exact bucket bounds
|
||||
and coverage. Display settings are saved with the layout.
|
||||
|
||||
Other widgets can call `MainWindow::showDataAnalysis(start, end)` or
|
||||
`DataAnalysisWidget::showRange(start, end)` with **inclusive** endpoints. Internally,
|
||||
`DataAnalysis::Range` and graph signals use half-open ranges.
|
||||
|
||||
Run the focused tests with:
|
||||
|
||||
```sh
|
||||
make -C tests/dataanalysis -j8 > /dev/null
|
||||
QT_QPA_PLATFORM=offscreen tests/dataanalysis/build/test_dataanalysis
|
||||
```
|
||||
@@ -0,0 +1,264 @@
|
||||
Iaito Deep Links
|
||||
================
|
||||
|
||||
Iaito registers the `iaito://` URI scheme so binaries, projects, samples and raw
|
||||
bytes can be referenced with stable, shareable links that open a specific
|
||||
location and view. Clicking such a link (from a browser, file manager, chat
|
||||
client, terminal, etc.) launches Iaito and navigates straight to the target.
|
||||
|
||||
The implementation lives in a single, self-contained pair of files,
|
||||
`src/common/DeepLink.{h,cpp}`, so it is easy to find, maintain and remove.
|
||||
|
||||
|
||||
URI format
|
||||
----------
|
||||
|
||||
```
|
||||
iaito://<handler>/<payload>?<query>
|
||||
```
|
||||
|
||||
There are exactly four top-level handlers:
|
||||
|
||||
| Handler | Payload | Opens |
|
||||
|----------------|------------------|------------------------------------------|
|
||||
| `file` | absolute path | a file from disk |
|
||||
| `project` | project name | a saved radare2/iaito project |
|
||||
| `sha256` | 64 hex chars | a locally registered sample by its hash |
|
||||
| `bytes` | hex byte string | an in-memory buffer built from raw bytes |
|
||||
|
||||
Only **absolute paths** are accepted for `file`.
|
||||
|
||||
As a convenience, if the host part of the URI is empty the absolute path is
|
||||
treated as a `file` reference:
|
||||
|
||||
```
|
||||
iaito:///bin/ls is the same as iaito://file/bin/ls
|
||||
```
|
||||
|
||||
Examples:
|
||||
|
||||
```
|
||||
iaito://file/tmp/a.out
|
||||
iaito://project/r2
|
||||
iaito://sha256/0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
|
||||
iaito://bytes/554889e54883ec10
|
||||
```
|
||||
|
||||
|
||||
Query parameters
|
||||
----------------
|
||||
|
||||
Parameters are passed as standard URL query parameters (`?a=1&b=2`), not as
|
||||
`#fragments`. Unknown parameters are ignored, so the format can be extended in
|
||||
the future without breaking existing links.
|
||||
|
||||
### Navigation
|
||||
|
||||
| Param | Meaning |
|
||||
|---------|------------------------------------------------------------------------|
|
||||
| `addr` | Any expression radare2 can resolve via `s <expr>` |
|
||||
| `view` | The panel to raise after opening |
|
||||
| `size` | Region length / block size (integer, decimal or `0x` hex) |
|
||||
|
||||
`addr` accepts anything radare2 understands:
|
||||
|
||||
```
|
||||
addr=0x401000
|
||||
addr=sym.main
|
||||
addr=entry0
|
||||
addr=str.Hello
|
||||
```
|
||||
|
||||
`view` may be one of:
|
||||
|
||||
```
|
||||
disasm graph hex decompiler
|
||||
strings imports exports functions
|
||||
```
|
||||
|
||||
`disasm`, `graph`, `hex` and `decompiler` open/raise the corresponding memory
|
||||
widget; the other names raise the matching tool panel by name.
|
||||
|
||||
`size` sets the current block size (radare2 `b`), e.g. `size=64` or `size=0x100`.
|
||||
|
||||
### Analysis (mostly for `bytes` and raw/unknown binaries)
|
||||
|
||||
| Param | Examples |
|
||||
|----------|-----------------------------------------------------|
|
||||
| `arch` | `x86`, `arm`, `arm64`, `mips`, `riscv`, `ppc` |
|
||||
| `bits` | `16`, `32`, `64` |
|
||||
| `endian` | `little`, `big` |
|
||||
| `cpu` | `generic`, `cortex`, `avr`, `68000`, `v850` |
|
||||
| `os` | `linux`, `darwin`, `windows`, `ios`, `android` |
|
||||
| `base` | base/map address, e.g. `0`, `0x1000`, `0x401000` |
|
||||
|
||||
For `bytes` links Iaito builds an in-memory `malloc://` buffer, writes the
|
||||
provided bytes into it and configures the session accordingly:
|
||||
|
||||
| Query param | radare2 config |
|
||||
|-------------|----------------|
|
||||
| `arch` | `asm.arch` |
|
||||
| `bits` | `asm.bits` |
|
||||
| `endian` | `cfg.bigendian`|
|
||||
| `cpu` | `asm.cpu` |
|
||||
| `os` | `asm.os` |
|
||||
| `base` | `bin.baddr` |
|
||||
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
Open a file and jump to an address:
|
||||
|
||||
```
|
||||
iaito://file/tmp/a.out?addr=0x401000
|
||||
```
|
||||
|
||||
Open a project and show the graph view at `main`:
|
||||
|
||||
```
|
||||
iaito://project/r2?addr=sym.main&view=graph
|
||||
```
|
||||
|
||||
Open a sample by SHA256 and show the decompiler:
|
||||
|
||||
```
|
||||
iaito://sha256/<hash>?addr=sym.main&view=decompiler
|
||||
```
|
||||
|
||||
Open a sample by SHA256 and select a region in the hex view:
|
||||
|
||||
```
|
||||
iaito://sha256/<hash>?addr=0x401000&size=0x100&view=hex
|
||||
```
|
||||
|
||||
Disassemble raw x86-64 shellcode:
|
||||
|
||||
```
|
||||
iaito://bytes/554889e54883ec10?arch=x86&bits=64&view=disasm
|
||||
```
|
||||
|
||||
Disassemble little-endian ARM code:
|
||||
|
||||
```
|
||||
iaito://bytes/e92d4800e8bd8800?arch=arm&bits=32&endian=little
|
||||
```
|
||||
|
||||
Analyze a raw big-endian MIPS firmware blob mapped at a base address:
|
||||
|
||||
```
|
||||
iaito://bytes/<hex>?arch=mips&bits=32&endian=big&base=0x80000000
|
||||
```
|
||||
|
||||
Open bytes and jump to an offset in the hex view:
|
||||
|
||||
```
|
||||
iaito://bytes/<hex>?arch=x86&bits=64&addr=0x10&view=hex
|
||||
```
|
||||
|
||||
|
||||
SHA256 samples database
|
||||
------------------------
|
||||
|
||||
`sha256` links resolve a hash to a local file through Iaito's samples database,
|
||||
which maps the SHA256 of every binary it has seen to its path on disk. Files are
|
||||
registered automatically when opened and can be managed from the *Sha256* tab in
|
||||
the New File dialog. If the hash is unknown, Iaito shows a dialog explaining how
|
||||
to register it; downloading unknown samples is out of scope.
|
||||
|
||||
See [sha256.md](sha256.md) for the on-disk layout, hashing, how to populate the
|
||||
database, and the configurable online lookup URL.
|
||||
|
||||
|
||||
Registering the URI handler
|
||||
---------------------------
|
||||
|
||||
The `iaito://` scheme must be associated with the Iaito executable at the OS
|
||||
level. This happens automatically with the standard install targets, and can
|
||||
also be run on its own.
|
||||
|
||||
### Make targets
|
||||
|
||||
```
|
||||
make install-deeplink # register iaito:// for the current user
|
||||
make uninstall-deeplink # remove the association
|
||||
```
|
||||
|
||||
`make user-install` registers the handler as part of installation, and
|
||||
`make uninstall` / `make user-uninstall` removes it. A system-wide
|
||||
`make install` refreshes the desktop database so the `.desktop` MimeType
|
||||
association is picked up.
|
||||
|
||||
### Linux
|
||||
|
||||
Registration relies on the desktop entry `src/org.radare.iaito.desktop`, which
|
||||
declares:
|
||||
|
||||
```
|
||||
Exec=iaito %u
|
||||
MimeType=x-scheme-handler/iaito;
|
||||
```
|
||||
|
||||
`scripts/deeplink/register.sh` runs `xdg-mime default` and
|
||||
`update-desktop-database`; `scripts/deeplink/unregister.sh` removes the
|
||||
association from `~/.config/mimeapps.list`.
|
||||
|
||||
### macOS
|
||||
|
||||
The app bundle declares the scheme in `Info.plist` via `CFBundleURLTypes`
|
||||
(`CFBundleURLSchemes = iaito`). LaunchServices registers it automatically when
|
||||
the bundle is installed; `scripts/deeplink/register.sh` forces a refresh with
|
||||
`lsregister -f <iaito.app>`. The handler is dropped when the bundle is removed.
|
||||
|
||||
`make run` launches the build without registering the URI handler. To optionally
|
||||
register the local bundle without installing it, run:
|
||||
|
||||
```sh
|
||||
sh scripts/deeplink/register.sh "$PWD/build/iaito.app"
|
||||
```
|
||||
|
||||
### Windows
|
||||
|
||||
`scripts/deeplink/register.bat` writes the `HKCU\Software\Classes\iaito` keys
|
||||
(including `URL Protocol` and `shell\open\command`) and
|
||||
`scripts/deeplink/unregister.bat` deletes them. Pass the path to `iaito.exe` as
|
||||
the first argument:
|
||||
|
||||
```
|
||||
scripts\deeplink\register.bat "C:\Program Files\iaito\iaito.exe"
|
||||
scripts\deeplink\unregister.bat
|
||||
```
|
||||
|
||||
|
||||
Using deep links from the UI
|
||||
----------------------------
|
||||
|
||||
Every code view and listing exposes a **Copy deep link** action that builds a
|
||||
link to the current address:
|
||||
|
||||
- Disassembly, decompiler and graph context menus (under *Copy* -> *Deep link*),
|
||||
embedding the matching `view=` value (`disasm`, `decompiler`, `graph`).
|
||||
- Hexdump context menu (*Copy* -> *Deep link*, `view=hex`).
|
||||
- The listing widgets (functions, symbols, imports, exports, strings, relocs,
|
||||
sections, ...) via the *Copy deep link* entry in their context menu.
|
||||
|
||||
When triggered, a dialog asks how the open file should be referenced: by loaded
|
||||
project name, by `sha256`, or by absolute `file` path. The resulting URI is
|
||||
placed on the clipboard, ready to paste into a report or a comment.
|
||||
|
||||
Links pasted into comments are rendered as clickable text in the disassembly and
|
||||
decompiler views: hovering shows a pointing-hand cursor and a left click follows
|
||||
the link. A link that points back into the file already open navigates in place
|
||||
(seek + view switch) instead of reopening the binary.
|
||||
|
||||
Notes
|
||||
-----
|
||||
|
||||
- `addr` is resolved through radare2's `s` command, so it accepts the full range
|
||||
of radare2 address expressions (flags, symbols, math, etc.).
|
||||
- `size` is always resolved to a number before use, so it cannot inject
|
||||
additional radare2 commands.
|
||||
- Query parameters are preferred over `#fragments`; future parameters can be
|
||||
added without changing the URI format.
|
||||
- The four handlers (`file`, `project`, `sha256`, `bytes`) are intentionally the
|
||||
only top-level entry points.
|
||||
@@ -0,0 +1,94 @@
|
||||
Iaito SHA256 Samples Database
|
||||
=============================
|
||||
|
||||
Iaito keeps a local database that maps the SHA256 of every binary it has seen to
|
||||
its path on disk. It powers `iaito://sha256/<hash>` deep links (see
|
||||
[deeplinks.md](deeplinks.md)) and the *Sha256* tab in the New File dialog, and it
|
||||
lets you look a sample up online (VirusTotal by default).
|
||||
|
||||
The implementation lives in `src/common/SamplesDB.{h,cpp}`.
|
||||
|
||||
|
||||
On-disk layout
|
||||
--------------
|
||||
|
||||
The database lives under Iaito's application data directory
|
||||
(`QStandardPaths::AppDataLocation`, e.g. `~/.local/share/radareorg/iaito/samples/`
|
||||
on Linux).
|
||||
|
||||
To scale to large collections without a single huge index file, entries are
|
||||
sharded git/rainbow-table style across two hash-prefix directory levels. The
|
||||
leaf is a plain text bucket file holding one `"<sha256> <absolute-path>"` entry
|
||||
per line:
|
||||
|
||||
```
|
||||
samples/<h0:2>/<h2:4>/<h0:4>
|
||||
|
||||
samples/53/a9/53a9 -> 53a9c0557a948069...172161625 /samples/a.out
|
||||
53a9ffeede... /samples/b.bin
|
||||
```
|
||||
|
||||
- The two directory levels are the first two and next two hex characters of the
|
||||
hash, giving up to 65536 buckets so no single directory grows unbounded.
|
||||
- The leaf bucket file is named after the 4-character prefix and contains all
|
||||
entries sharing it, hash and absolute path separated by a single space.
|
||||
- Lookups read just one bucket file; the whole database is only walked when the
|
||||
*Sha256* tab lists every entry.
|
||||
|
||||
When the same hash is registered again with a different path, the latest path
|
||||
wins (one path per hash).
|
||||
|
||||
|
||||
Hashing
|
||||
-------
|
||||
|
||||
`SamplesDB::sha256File()` computes the digest with radare2's `r_muta` SHA256
|
||||
session API, reading the file in fixed-size blocks. Memory stays bounded
|
||||
regardless of file size, so even multi-gigabyte files hash safely without being
|
||||
loaded into memory at once.
|
||||
|
||||
|
||||
Populating the database
|
||||
-----------------------
|
||||
|
||||
- **Automatically** — every file you open in Iaito is registered (sha256 -> path)
|
||||
once it finishes loading, from `MainWindow::finalizeOpen()`. Hashing runs on a
|
||||
background thread so the UI stays responsive on large files. Live debug
|
||||
sessions and non-file targets (`malloc://`, `dbg://`, ...) are skipped, as are
|
||||
paths that no longer exist.
|
||||
- **Manually** — the *Sha256* tab in the New File dialog lets you:
|
||||
- **Scan folder…** — recursively register every file under a chosen directory
|
||||
(runs in the background).
|
||||
- **Filter** — the input field filters the list by file name or hash as you
|
||||
type.
|
||||
- **On disk** — a column shows whether each registered file still exists.
|
||||
- **Open** — open the selected sample (also on double-click).
|
||||
- **Lookup** — open the configured lookup URL for the selected hash.
|
||||
- **Delete** — remove the selected entry from the database (the file is left
|
||||
untouched).
|
||||
- **Right-click** — *Copy path*, *Copy hash*, *Open containing folder*,
|
||||
*Open in lookup URL*, *Remove from database*.
|
||||
|
||||
|
||||
Online lookup URL
|
||||
-----------------
|
||||
|
||||
The *Lookup* button and the *Open in lookup URL* context action open a base URL
|
||||
with the selected file's SHA256 appended. The base URL defaults to VirusTotal:
|
||||
|
||||
```
|
||||
https://www.virustotal.com/gui/file/<sha256>
|
||||
```
|
||||
|
||||
It is configurable in *Settings -> Samples* (stored as `sha256.lookupBaseUrl`),
|
||||
where a *Reset* button restores the VirusTotal default. This lets you point the
|
||||
lookup at any service that resolves a file by its SHA256.
|
||||
|
||||
|
||||
Deep link resolution
|
||||
---------------------
|
||||
|
||||
`iaito://sha256/<hash>` looks the hash up in the database and opens the mapped
|
||||
file if it still exists, applying any `addr`/`view`/`size` parameters. If the
|
||||
hash is unknown, Iaito shows a dialog explaining how to register it. Downloading
|
||||
unknown samples is intentionally out of scope.
|
||||
@@ -0,0 +1,30 @@
|
||||
# Structures in the hexdump
|
||||
|
||||
Open the hexdump sidebar's **Structures** tab, choose an r2 type, enter an address
|
||||
or expression, and press **Inspect**. **At cursor** uses the first selected byte
|
||||
or the current cursor. You can also right-click the hexdump and choose **Inspect
|
||||
structure here**; a type already linked to that address is selected automatically.
|
||||
|
||||
The tree shows each field's declared type, value, address, and size. Expand nested
|
||||
structures, unions, arrays, and pointers to explore them. Selecting a field selects
|
||||
its bytes in the hexdump. Field colors match the byte highlights. The inspected
|
||||
structure stays anchored while you navigate, and **Refresh** preserves expanded
|
||||
fields and the selected row while rereading memory and type definitions.
|
||||
|
||||
Types come from r2's type database, including imported C definitions and typedefs.
|
||||
The tree uses stored member offsets and the current `asm.bits` and `cfg.bigendian`
|
||||
settings. Large arrays are divided into expandable groups. Unmapped pointers and
|
||||
recursive references stop expansion; invalid or overflowing layouts are reported.
|
||||
Array expressions such as `uint16_t[16]` can be inspected directly.
|
||||
|
||||
**Pin type** creates an r2 type link (`tl`) at the inspected address. The pinned
|
||||
address list also includes existing type links and command annotations (`Cr`) from
|
||||
the current r2 session. Define a named typedef before pinning an array expression.
|
||||
|
||||
**Pin command…** stores a command at the address using the selected byte range or
|
||||
the chosen type's size. Double-click a pin to navigate to its address. Select a
|
||||
command pin and press **Run command** to display its output. **Unpin** removes only
|
||||
the selected type link or command annotation. Pins use r2's existing project data
|
||||
and are saved with the project. iaito also writes `iaito-memory-pins.json` inside the
|
||||
project directory to preserve types and pins omitted by some r2 binary project versions;
|
||||
keep that companion file when copying a project.
|
||||
|
Before Width: | Height: | Size: 138 KiB After Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 175 KiB After Width: | Height: | Size: 130 KiB |
|
Before Width: | Height: | Size: 138 KiB After Width: | Height: | Size: 107 KiB |
|
Before Width: | Height: | Size: 143 KiB After Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 674 KiB |
@@ -0,0 +1,14 @@
|
||||
@echo off
|
||||
rem Register the iaito:// URI scheme handler for the current user.
|
||||
rem Usage: register.bat [path-to-iaito.exe]
|
||||
|
||||
setlocal
|
||||
set "IAITO=%~1"
|
||||
if "%IAITO%"=="" set "IAITO=%~dp0..\..\build\iaito.exe"
|
||||
|
||||
reg add "HKCU\Software\Classes\iaito" /ve /d "URL:iaito Protocol" /f
|
||||
reg add "HKCU\Software\Classes\iaito" /v "URL Protocol" /d "" /f
|
||||
reg add "HKCU\Software\Classes\iaito\DefaultIcon" /ve /d "\"%IAITO%\",0" /f
|
||||
reg add "HKCU\Software\Classes\iaito\shell\open\command" /ve /d "\"%IAITO%\" \"%%1\"" /f
|
||||
echo Registered iaito:// -^> %IAITO%
|
||||
endlocal
|
||||
@@ -0,0 +1,28 @@
|
||||
#!/bin/sh
|
||||
# Register the iaito:// URI scheme handler on Linux and macOS.
|
||||
# Usage: register.sh [path-to-iaito.app] (the argument is only used on macOS)
|
||||
|
||||
case "$(uname)" in
|
||||
Darwin)
|
||||
APP="$1"
|
||||
[ -d "$APP" ] || APP="/Applications/iaito.app"
|
||||
[ -d "$APP" ] || APP="$HOME/Applications/iaito.app"
|
||||
if [ ! -d "$APP" ]; then
|
||||
echo "Cannot find iaito.app, pass its path as first argument" >&2
|
||||
exit 1
|
||||
fi
|
||||
LSREG="/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister"
|
||||
"$LSREG" -f "$APP"
|
||||
echo "Registered iaito:// for $APP"
|
||||
;;
|
||||
*)
|
||||
DESKTOP="org.radare.iaito.desktop"
|
||||
if command -v xdg-mime >/dev/null 2>&1; then
|
||||
xdg-mime default "$DESKTOP" x-scheme-handler/iaito
|
||||
fi
|
||||
if command -v update-desktop-database >/dev/null 2>&1; then
|
||||
update-desktop-database "$HOME/.local/share/applications" 2>/dev/null || true
|
||||
fi
|
||||
echo "Registered iaito:// -> $DESKTOP"
|
||||
;;
|
||||
esac
|
||||
@@ -0,0 +1,4 @@
|
||||
@echo off
|
||||
rem Unregister the iaito:// URI scheme handler for the current user.
|
||||
reg delete "HKCU\Software\Classes\iaito" /f
|
||||
echo Unregistered iaito://
|
||||
@@ -0,0 +1,19 @@
|
||||
#!/bin/sh
|
||||
# Unregister the iaito:// URI scheme handler on Linux and macOS.
|
||||
|
||||
case "$(uname)" in
|
||||
Darwin)
|
||||
echo "macOS drops the iaito:// handler when the iaito.app bundle is removed."
|
||||
;;
|
||||
*)
|
||||
MIMEAPPS="$HOME/.config/mimeapps.list"
|
||||
if [ -f "$MIMEAPPS" ]; then
|
||||
sed -i.bak '/x-scheme-handler\/iaito=/d' "$MIMEAPPS"
|
||||
rm -f "$MIMEAPPS.bak"
|
||||
fi
|
||||
if command -v update-desktop-database >/dev/null 2>&1; then
|
||||
update-desktop-database "$HOME/.local/share/applications" 2>/dev/null || true
|
||||
fi
|
||||
echo "Unregistered iaito:// handler"
|
||||
;;
|
||||
esac
|
||||
@@ -1,4 +1,6 @@
|
||||
FROM ubuntu:24.04
|
||||
RUN apt-get update && apt-get install -y clang-format
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends clang-format-20 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
WORKDIR /project
|
||||
ENTRYPOINT ["clang-format"]
|
||||
ENTRYPOINT ["clang-format-20"]
|
||||
@@ -1,2 +1,5 @@
|
||||
IMAGE_NAME ?= clang-format-image
|
||||
IMAGE_TAG ?= llvm20
|
||||
|
||||
all:
|
||||
docker build -t clang-format-image .
|
||||
docker build -t $(IMAGE_NAME):$(IMAGE_TAG) .
|
||||
@@ -1,4 +1,4 @@
|
||||
#include <r_core.h>
|
||||
#include "common/Radare2Compat.h"
|
||||
|
||||
extern int main_iaito();
|
||||
static int r_cmd_anal_call(void *user, const char *input)
|
||||
@@ -26,15 +26,10 @@ RCorePlugin r_core_plugin_iaito = {
|
||||
};
|
||||
|
||||
#ifndef R2_PLUGIN_INCORE
|
||||
#if R2_VERSION_NUMBER >= 50909
|
||||
R_API RLibStruct radare_plugin
|
||||
= {.type = R_LIB_TYPE_CORE,
|
||||
.data = &r_core_plugin_iaito,
|
||||
.version = R2_VERSION,
|
||||
.abiver = R2_ABIVERSION};
|
||||
#else
|
||||
R_API RLibStruct radare_plugin
|
||||
= {.type = R_LIB_TYPE_CORE, .data = &r_core_plugin_iaito, .version = R2_VERSION};
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
@@ -3,21 +3,28 @@ TEMPLATE = app
|
||||
TARGET = iaito
|
||||
|
||||
IAITO_VERSION_MAJOR = 6
|
||||
IAITO_VERSION_MINOR = 0
|
||||
IAITO_VERSION_PATCH = 2
|
||||
IAITO_VERSION_MINOR = 2
|
||||
IAITO_VERSION_PATCH = 6
|
||||
|
||||
CONFIG += c++20
|
||||
QMAKE_CXXFLAGS += -std=c++20
|
||||
QMAKE_CXXFLAGS += -include $$absolute_path(common/CompilerWorkarounds.h, $$PWD)
|
||||
|
||||
CONFIG += app_bundle
|
||||
# LIBS+= -dead_strip
|
||||
|
||||
CONFIG += sdk_no_version_check
|
||||
|
||||
unix:!macx|macx:!IAITO_BUNDLE_R2_APPBUNDLE {
|
||||
QMAKE_RPATHDIR += /usr/local/lib
|
||||
!defined(IAITO_BUNDLE_R2_APPBUNDLE, var) IAITO_BUNDLE_R2_APPBUNDLE=false
|
||||
equals(IAITO_BUNDLE_R2_APPBUNDLE, true) CONFIG += IAITO_BUNDLE_R2_APPBUNDLE
|
||||
|
||||
unix:!IAITO_BUNDLE_R2_APPBUNDLE {
|
||||
isEmpty(R2_LIBDIR) {
|
||||
R2_LIBDIR = $$system(r2 -H R2_LIBDIR)
|
||||
}
|
||||
QMAKE_RPATHDIR += $${R2_LIBDIR}
|
||||
QMAKE_LFLAGS_RPATH=
|
||||
QMAKE_LFLAGS += "-Wl,-rpath,/usr/local/lib"
|
||||
QMAKE_LFLAGS += "-Wl,-rpath,$${R2_LIBDIR}"
|
||||
}
|
||||
|
||||
QMAKE_CXXFLAGS += $$(CXXFLAGS)
|
||||
@@ -34,8 +41,7 @@ QMAKE_CXXFLAGS += -g -O0
|
||||
VERSION = $${IAITO_VERSION_MAJOR}.$${IAITO_VERSION_MINOR}.$${IAITO_VERSION_PATCH}
|
||||
|
||||
# Required QT version
|
||||
lessThan(QT_MAJOR_VERSION, 5): error("requires Qt 5")
|
||||
# Doesnt build for Qt6 yet... but will do soon
|
||||
lessThan(QT_MAJOR_VERSION, 5): error("requires at least Qt 5")
|
||||
|
||||
# Icon for OS X
|
||||
ICON = img/iaito.icns
|
||||
@@ -43,7 +49,7 @@ ICON = img/iaito.icns
|
||||
# Icon/resources for Windows
|
||||
win32: RC_ICONS = img/iaito.ico
|
||||
|
||||
QT += core gui widgets svg network
|
||||
QT += widgets svg network
|
||||
QT_CONFIG -= no-pkg-config
|
||||
|
||||
greaterThan(QT_MAJOR_VERSION, 5): QT += svgwidgets
|
||||
@@ -51,19 +57,8 @@ greaterThan(QT_MAJOR_VERSION, 5): QT += svgwidgets
|
||||
!defined(IAITO_ENABLE_CRASH_REPORTS, var) IAITO_ENABLE_CRASH_REPORTS=false
|
||||
equals(IAITO_ENABLE_CRASH_REPORTS, true) CONFIG += IAITO_ENABLE_CRASH_REPORTS
|
||||
|
||||
!defined(IAITO_ENABLE_PYTHON, var) IAITO_ENABLE_PYTHON=false
|
||||
equals(IAITO_ENABLE_PYTHON, true) CONFIG += IAITO_ENABLE_PYTHON
|
||||
IAITO_ENABLE_PYTHON=false
|
||||
|
||||
!defined(IAITO_ENABLE_PYTHON_BINDINGS, var) IAITO_ENABLE_PYTHON_BINDINGS=false
|
||||
equals(IAITO_ENABLE_PYTHON, true) {
|
||||
equals(IAITO_ENABLE_PYTHON_BINDINGS, true) {
|
||||
CONFIG += IAITO_ENABLE_PYTHON_BINDINGS
|
||||
}
|
||||
}
|
||||
|
||||
!defined(IAITO_BUNDLE_R2_APPBUNDLE, var) IAITO_BUNDLE_R2_APPBUNDLE=false
|
||||
equals(IAITO_BUNDLE_R2_APPBUNDLE, true) CONFIG += IAITO_BUNDLE_R2_APPBUNDLE
|
||||
!defined(IAITO_ENABLE_DEBUGGER, var) IAITO_ENABLE_DEBUGGER=true
|
||||
equals(IAITO_ENABLE_DEBUGGER, true) CONFIG += IAITO_ENABLE_DEBUGGER
|
||||
|
||||
!defined(IAITO_APPVEYOR_R2DEC, var) IAITO_APPVEYOR_R2DEC=false
|
||||
equals(IAITO_APPVEYOR_R2DEC, true) CONFIG += IAITO_APPVEYOR_R2DEC
|
||||
@@ -73,9 +68,6 @@ equals(IAITO_R2GHIDRA_STATIC, true) CONFIG += IAITO_R2GHIDRA_STATIC
|
||||
|
||||
DEFINES += IAITO_SOURCE_BUILD
|
||||
|
||||
|
||||
# crash reports and python code has been removed and its unmaintained,
|
||||
# so let's just disable the messages that are printed out
|
||||
IAITO_ENABLE_CRASH_REPORTS {
|
||||
message("Crash report support enabled.")
|
||||
DEFINES += IAITO_ENABLE_CRASH_REPORTS
|
||||
@@ -83,27 +75,11 @@ IAITO_ENABLE_CRASH_REPORTS {
|
||||
# message("Crash report support disabled.")
|
||||
}
|
||||
|
||||
IAITO_ENABLE_PYTHON {
|
||||
message("Python enabled.")
|
||||
DEFINES += IAITO_ENABLE_PYTHON
|
||||
IAITO_ENABLE_DEBUGGER {
|
||||
message("Native debugger support enabled.")
|
||||
DEFINES += IAITO_ENABLE_DEBUGGER
|
||||
} else {
|
||||
# message("Python disabled.")
|
||||
}
|
||||
|
||||
IAITO_ENABLE_PYTHON_BINDINGS {
|
||||
message("Python Bindings enabled.")
|
||||
DEFINES += IAITO_ENABLE_PYTHON_BINDINGS
|
||||
} else {
|
||||
# message("Python Bindings disabled. (requires IAITO_ENABLE_PYTHON=true)")
|
||||
}
|
||||
|
||||
win32:defined(IAITO_DEPS_DIR, var) {
|
||||
!defined(SHIBOKEN_EXECUTABLE, var) SHIBOKEN_EXECUTABLE="$${IAITO_DEPS_DIR}/pyside/bin/shiboken2.exe"
|
||||
!defined(SHIBOKEN_INCLUDEDIR, var) SHIBOKEN_INCLUDEDIR="$${IAITO_DEPS_DIR}/pyside/include/shiboken2"
|
||||
!defined(SHIBOKEN_LIBRARY, var) SHIBOKEN_LIBRARY="$${IAITO_DEPS_DIR}/pyside/lib/shiboken2.abi3.lib"
|
||||
!defined(PYSIDE_INCLUDEDIR, var) PYSIDE_INCLUDEDIR="$${IAITO_DEPS_DIR}/pyside/include/PySide2"
|
||||
!defined(PYSIDE_LIBRARY, var) PYSIDE_LIBRARY="$${IAITO_DEPS_DIR}/pyside/lib/pyside2.abi3.lib"
|
||||
!defined(PYSIDE_TYPESYSTEMS, var) PYSIDE_TYPESYSTEMS="$${IAITO_DEPS_DIR}/pyside/share/PySide2/typesystems"
|
||||
message("Native debugger support disabled.")
|
||||
}
|
||||
|
||||
INCLUDEPATH *= . core widgets dialogs common plugins menus
|
||||
@@ -124,13 +100,12 @@ macx {
|
||||
QMAKE_INFO_PLIST = macos/Info.plist
|
||||
}
|
||||
|
||||
unix:exists(/usr/local/include/libr)|bsd:exists(/usr/local/include/libr) {
|
||||
INCLUDEPATH += /usr/local/include/libr
|
||||
INCLUDEPATH += /usr/local/include/libr/sdb
|
||||
}
|
||||
unix:exists(/usr/include/libr) {
|
||||
INCLUDEPATH += /usr/include/libr
|
||||
INCLUDEPATH += /usr/include/libr/sdb
|
||||
unix {
|
||||
isEmpty(R2_INCDIR) {
|
||||
R2_INCDIR = $$system(r2 -H R2_INCDIR)
|
||||
}
|
||||
INCLUDEPATH += $${R2_INCDIR}
|
||||
INCLUDEPATH += $${R2_INCDIR}/sdb
|
||||
}
|
||||
unix {
|
||||
QMAKE_LFLAGS += -rdynamic # Export dynamic symbols for plugins
|
||||
@@ -143,132 +118,6 @@ include(lib_radare2.pri)
|
||||
CONFIG += link_pkgconfig
|
||||
}
|
||||
|
||||
IAITO_ENABLE_PYTHON {
|
||||
win32 {
|
||||
PYTHON_EXECUTABLE = $$system("where python", lines)
|
||||
PYTHON_EXECUTABLE = $$first(PYTHON_EXECUTABLE)
|
||||
pythonpath = $$clean_path($$dirname(PYTHON_EXECUTABLE))
|
||||
LIBS += -L$${pythonpath}/libs -lpython3
|
||||
INCLUDEPATH += $${pythonpath}/include
|
||||
}
|
||||
|
||||
unix|macx|bsd {
|
||||
defined(PYTHON_FRAMEWORK_DIR, var) {
|
||||
message("Using Python.framework at $$PYTHON_FRAMEWORK_DIR")
|
||||
INCLUDEPATH += $$PYTHON_FRAMEWORK_DIR/Python.framework/Headers
|
||||
LIBS += -F$$PYTHON_FRAMEWORK_DIR -framework Python
|
||||
DEFINES += MACOS_PYTHON_FRAMEWORK_BUNDLED
|
||||
} else {
|
||||
!packagesExist(python3) {
|
||||
error("ERROR: Python 3 could not be found. Make sure it is available to pkg-config.")
|
||||
}
|
||||
PKGCONFIG += python3
|
||||
}
|
||||
}
|
||||
|
||||
IAITO_ENABLE_PYTHON_BINDINGS {
|
||||
isEmpty(SHIBOKEN_EXECUTABLE):!packagesExist(shiboken2) {
|
||||
error("ERROR: Shiboken2, which is required to build the Python Bindings, could not be found. Make sure it is available to pkg-config.")
|
||||
}
|
||||
isEmpty(PYSIDE_LIBRARY):!packagesExist(pyside2) {
|
||||
error("ERROR: PySide2, which is required to build the Python Bindings, could not be found. Make sure it is available to pkg-config.")
|
||||
}
|
||||
win32 {
|
||||
BINDINGS_SRC_LIST_CMD = "\"$${PYTHON_EXECUTABLE}\" bindings/src_list.py"
|
||||
} else {
|
||||
BINDINGS_SRC_LIST_CMD = "python3 bindings/src_list.py"
|
||||
}
|
||||
BINDINGS_SRC_DIR = "$${PWD}/bindings"
|
||||
BINDINGS_BUILD_DIR = "$${OUT_PWD}/bindings"
|
||||
BINDINGS_SOURCE_DIR = "$${BINDINGS_BUILD_DIR}/IaitoBindings"
|
||||
BINDINGS_SOURCE = $$system("$${BINDINGS_SRC_LIST_CMD} qmake \"$${BINDINGS_BUILD_DIR}\"")
|
||||
BINDINGS_INCLUDE_DIRS = "$$[QT_INSTALL_HEADERS]" \
|
||||
"$$[QT_INSTALL_HEADERS]/QtCore" \
|
||||
"$$[QT_INSTALL_HEADERS]/QtWidgets" \
|
||||
"$$[QT_INSTALL_HEADERS]/QtGui"
|
||||
for (path, R2_INCLUDEPATH) {
|
||||
BINDINGS_INCLUDE_DIRS += "$$path"
|
||||
}
|
||||
for(path, INCLUDEPATH) {
|
||||
BINDINGS_INCLUDE_DIRS += $$absolute_path("$$path")
|
||||
}
|
||||
|
||||
win32 {
|
||||
PATH_SEP = ";"
|
||||
} else {
|
||||
PATH_SEP = ":"
|
||||
}
|
||||
BINDINGS_INCLUDE_DIRS = $$join(BINDINGS_INCLUDE_DIRS, $$PATH_SEP)
|
||||
|
||||
isEmpty(SHIBOKEN_EXECUTABLE) {
|
||||
SHIBOKEN_EXECUTABLE = $$system("pkg-config --variable=generator_location shiboken2")
|
||||
}
|
||||
|
||||
isEmpty(PYSIDE_TYPESYSTEMS) {
|
||||
PYSIDE_TYPESYSTEMS = $$system("pkg-config --variable=typesystemdir pyside2")
|
||||
}
|
||||
isEmpty(PYSIDE_INCLUDEDIR) {
|
||||
PYSIDE_INCLUDEDIR = $$system("pkg-config --variable=includedir pyside2")
|
||||
}
|
||||
|
||||
QMAKE_SUBSTITUTES += bindings/bindings.txt.in
|
||||
|
||||
SHIBOKEN_OPTIONS = --project-file="$${BINDINGS_BUILD_DIR}/bindings.txt"
|
||||
defined(SHIBOKEN_EXTRA_OPTIONS, var) SHIBOKEN_OPTIONS += $${SHIBOKEN_EXTRA_OPTIONS}
|
||||
|
||||
win32:SHIBOKEN_OPTIONS += --avoid-protected-hack
|
||||
bindings.target = bindings_target
|
||||
bindings.commands = $$quote($$system_path($${SHIBOKEN_EXECUTABLE})) $${SHIBOKEN_OPTIONS}
|
||||
QMAKE_EXTRA_TARGETS += bindings
|
||||
PRE_TARGETDEPS += bindings_target
|
||||
# GENERATED_SOURCES += $${BINDINGS_SOURCE} done by dummy targets bellow
|
||||
|
||||
INCLUDEPATH += "$${BINDINGS_SOURCE_DIR}"
|
||||
|
||||
win32:DEFINES += WIN32_LEAN_AND_MEAN
|
||||
|
||||
!isEmpty(PYSIDE_LIBRARY) {
|
||||
LIBS += "$$SHIBOKEN_LIBRARY" "$$PYSIDE_LIBRARY"
|
||||
INCLUDEPATH += "$$SHIBOKEN_INCLUDEDIR"
|
||||
} else:macx {
|
||||
# Hack needed because with regular PKGCONFIG qmake will mess up everything
|
||||
QMAKE_CXXFLAGS += $$system("pkg-config --cflags shiboken2 pyside2")
|
||||
LIBS += $$system("pkg-config --libs shiboken2 pyside2")
|
||||
} else {
|
||||
PKGCONFIG += shiboken2 pyside2
|
||||
}
|
||||
INCLUDEPATH += "$$PYSIDE_INCLUDEDIR" "$$PYSIDE_INCLUDEDIR/QtCore" "$$PYSIDE_INCLUDEDIR/QtWidgets" "$$PYSIDE_INCLUDEDIR/QtGui"
|
||||
|
||||
|
||||
BINDINGS_DUMMY_INPUT_LIST = bindings/src_list.py
|
||||
|
||||
# dummy rules to specify dependency between generated binding files and bindings_target
|
||||
bindings_h.input = BINDINGS_DUMMY_INPUT_LIST
|
||||
bindings_h.depends = bindings_target
|
||||
bindings_h.output = iaitobindings_python.h
|
||||
bindings_h.commands = "echo placeholder command ${QMAKE_FILE_OUT}"
|
||||
bindings_h.variable_out = HEADERS
|
||||
QMAKE_EXTRA_COMPILERS += bindings_h
|
||||
|
||||
for(path, BINDINGS_SOURCE) {
|
||||
dummy_input = $$replace(path, .cpp, .txt)
|
||||
BINDINGS_DUMMY_INPUTS += $$dummy_input
|
||||
win32 {
|
||||
_ = $$system("mkdir \"$$dirname(dummy_input)\"; echo a >\"$$dummy_input\"")
|
||||
} else {
|
||||
_ = $$system("mkdir -p \"$$dirname(dummy_input)\"; echo a >\"$$dummy_input\"")
|
||||
}
|
||||
}
|
||||
|
||||
bindings_cpp.input = BINDINGS_DUMMY_INPUTS
|
||||
bindings_cpp.depends = bindings_target
|
||||
bindings_cpp.output = "$${BINDINGS_SOURCE_DIR}/${QMAKE_FILE_IN_BASE}.cpp"
|
||||
bindings_cpp.commands = "echo placeholder command ${QMAKE_FILE_OUT}"
|
||||
bindings_cpp.variable_out = GENERATED_SOURCES
|
||||
QMAKE_EXTRA_COMPILERS += bindings_cpp
|
||||
}
|
||||
}
|
||||
|
||||
IAITO_ENABLE_CRASH_REPORTS {
|
||||
defined(BREAKPAD_FRAMEWORK_DIR, var)|defined(BREAKPAD_SOURCE_DIR, var) {
|
||||
defined(BREAKPAD_FRAMEWORK_DIR, var) {
|
||||
@@ -315,15 +164,20 @@ IAITO_R2GHIDRA_STATIC {
|
||||
QMAKE_SUBSTITUTES += IaitoConfig.h.in
|
||||
|
||||
SOURCES += \
|
||||
common/ExceptionInfo.cpp \
|
||||
widgets/ExceptionBar.cpp \
|
||||
Main.cpp \
|
||||
core/Iaito.cpp \
|
||||
dialogs/EditStringDialog.cpp \
|
||||
dialogs/DumpDialog.cpp \
|
||||
dialogs/WriteCommandsDialogs.cpp \
|
||||
widgets/DisassemblerGraphView.cpp \
|
||||
widgets/OverviewView.cpp \
|
||||
common/PreviewTooltip.cpp \
|
||||
common/RichTextPainter.cpp \
|
||||
dialogs/InitialOptionsDialog.cpp \
|
||||
dialogs/AboutDialog.cpp \
|
||||
dialogs/AddressSpaceManagerDialog.cpp \
|
||||
dialogs/CommentsDialog.cpp \
|
||||
dialogs/EditInstructionDialog.cpp \
|
||||
dialogs/FlagDialog.cpp \
|
||||
@@ -331,14 +185,17 @@ SOURCES += \
|
||||
dialogs/NativeDebugDialog.cpp \
|
||||
dialogs/XrefsDialog.cpp \
|
||||
core/MainWindow.cpp \
|
||||
core/DockManager.cpp \
|
||||
core/QtDockBackend.cpp \
|
||||
common/Helpers.cpp \
|
||||
common/TextEditDialog.cpp \
|
||||
common/Highlighter.cpp \
|
||||
common/MdHighlighter.cpp \
|
||||
common/Markdown.cpp \
|
||||
common/DirectionalComboBox.cpp \
|
||||
common/TypeScriptHighlighter.cpp \
|
||||
common/AnalTask.cpp \
|
||||
dialogs/preferences/AsmOptionsWidget.cpp \
|
||||
dialogs/settings/AsmOptionsWidget.cpp \
|
||||
dialogs/FortuneDialog.cpp \
|
||||
dialogs/NewFileDialog.cpp \
|
||||
widgets/CommentsWidget.cpp \
|
||||
@@ -349,6 +206,7 @@ SOURCES += \
|
||||
widgets/ExportsWidget.cpp \
|
||||
widgets/FlagsWidget.cpp \
|
||||
widgets/FunctionsWidget.cpp \
|
||||
widgets/GlobalVariablesWidget.cpp \
|
||||
widgets/ImportsWidget.cpp \
|
||||
widgets/CodeEditor.cpp \
|
||||
widgets/Omnibar.cpp \
|
||||
@@ -357,39 +215,51 @@ SOURCES += \
|
||||
widgets/SegmentsWidget.cpp \
|
||||
widgets/StringsWidget.cpp \
|
||||
widgets/SymbolsWidget.cpp \
|
||||
widgets/XrefsWidget.cpp \
|
||||
widgets/RefsWidget.cpp \
|
||||
widgets/CodeInfoWidget.cpp \
|
||||
menus/DisassemblyContextMenu.cpp \
|
||||
menus/DecompilerContextMenu.cpp \
|
||||
menus/ColorPickerMenu.cpp \
|
||||
menus/PinPickerMenu.cpp \
|
||||
widgets/DisassemblyWidget.cpp \
|
||||
widgets/HexdumpWidget.cpp \
|
||||
common/Configuration.cpp \
|
||||
common/theme/Theme.cpp \
|
||||
common/theme/IaitoStyle.cpp \
|
||||
common/Colors.cpp \
|
||||
dialogs/SaveProjectDialog.cpp \
|
||||
common/TempConfig.cpp \
|
||||
common/SvgIconEngine.cpp \
|
||||
common/SyntaxHighlighter.cpp \
|
||||
widgets/DecompilerWidget.cpp \
|
||||
widgets/DecompilerTextEdit.cpp \
|
||||
widgets/VisualNavbar.cpp \
|
||||
widgets/GraphView.cpp \
|
||||
dialogs/preferences/PreferencesDialog.cpp \
|
||||
dialogs/preferences/AppearanceOptionsWidget.cpp \
|
||||
dialogs/preferences/GraphOptionsWidget.cpp \
|
||||
dialogs/preferences/PreferenceCategory.cpp \
|
||||
dialogs/preferences/InitializationFileEditor.cpp \
|
||||
dialogs/settings/SettingsDialog.cpp \
|
||||
dialogs/settings/AppearanceOptionsWidget.cpp \
|
||||
dialogs/settings/GraphOptionsWidget.cpp \
|
||||
dialogs/settings/SettingCategory.cpp \
|
||||
dialogs/settings/InitializationFileEditor.cpp \
|
||||
widgets/QuickFilterView.cpp \
|
||||
widgets/ClassesWidget.cpp \
|
||||
widgets/ResourcesWidget.cpp \
|
||||
widgets/VTablesWidget.cpp \
|
||||
widgets/TypesWidget.cpp \
|
||||
widgets/HeadersWidget.cpp \
|
||||
widgets/InterfacesWidget.cpp \
|
||||
widgets/SearchWidget.cpp \
|
||||
IaitoApplication.cpp \
|
||||
common/PythonAPI.cpp \
|
||||
common/DeepLink.cpp \
|
||||
common/SamplesDB.cpp \
|
||||
dialogs/R2PluginsDialog.cpp \
|
||||
widgets/IaitoDockWidget.cpp \
|
||||
widgets/IaitoTreeWidget.cpp \
|
||||
widgets/MapsWidget.cpp \
|
||||
widgets/BinariesWidget.cpp \
|
||||
widgets/FilesWidget.cpp \
|
||||
widgets/FilesystemWidget.cpp \
|
||||
common/SourceLineReference.cpp \
|
||||
widgets/GraphWidget.cpp \
|
||||
widgets/OverviewWidget.cpp \
|
||||
common/JsonTreeItem.cpp \
|
||||
@@ -411,8 +281,10 @@ SOURCES += \
|
||||
dialogs/R2TaskDialog.cpp \
|
||||
widgets/DebugActions.cpp \
|
||||
widgets/MemoryMapWidget.cpp \
|
||||
dialogs/preferences/DebugOptionsWidget.cpp \
|
||||
dialogs/preferences/PluginsOptionsWidget.cpp \
|
||||
dialogs/settings/DebugOptionsWidget.cpp \
|
||||
dialogs/settings/WebserverOptionsWidget.cpp \
|
||||
dialogs/settings/SamplesOptionsWidget.cpp \
|
||||
dialogs/settings/PluginsOptionsWidget.cpp \
|
||||
widgets/BreakpointWidget.cpp \
|
||||
dialogs/BreakpointsDialog.cpp \
|
||||
dialogs/AttachProcDialog.cpp \
|
||||
@@ -420,10 +292,10 @@ SOURCES += \
|
||||
dialogs/SetToDataDialog.cpp \
|
||||
dialogs/EditVariablesDialog.cpp \
|
||||
dialogs/EditFunctionDialog.cpp \
|
||||
dialogs/SwitchJumpTableDialog.cpp \
|
||||
widgets/IaitoTreeView.cpp \
|
||||
widgets/ComboQuickFilterView.cpp \
|
||||
dialogs/HexdumpRangeDialog.cpp \
|
||||
common/QtResImporter.cpp \
|
||||
common/IaitoSeekable.cpp \
|
||||
common/RefreshDeferrer.cpp \
|
||||
dialogs/WelcomeDialog.cpp \
|
||||
@@ -431,7 +303,6 @@ SOURCES += \
|
||||
dialogs/EditMethodDialog.cpp \
|
||||
dialogs/TypesInteractionDialog.cpp \
|
||||
widgets/SdbWidget.cpp \
|
||||
common/PythonManager.cpp \
|
||||
plugins/PluginManager.cpp \
|
||||
common/BasicBlockHighlighter.cpp \
|
||||
common/BasicInstructionHighlighter.cpp \
|
||||
@@ -440,31 +311,40 @@ SOURCES += \
|
||||
common/ColorThemeWorker.cpp \
|
||||
widgets/ColorThemeComboBox.cpp \
|
||||
widgets/ColorThemeListView.cpp \
|
||||
dialogs/preferences/ColorThemeEditDialog.cpp \
|
||||
common/UpdateWorker.cpp \
|
||||
dialogs/settings/ColorThemeEditDialog.cpp \
|
||||
dialogs/settings/InterfaceThemeEditDialog.cpp \
|
||||
widgets/MemoryDockWidget.cpp \
|
||||
common/CrashHandler.cpp \
|
||||
common/BugReporting.cpp \
|
||||
common/HighDpiPixmap.cpp \
|
||||
widgets/GraphGridLayout.cpp \
|
||||
widgets/HexWidget.cpp \
|
||||
widgets/HexPatWidget.cpp \
|
||||
widgets/AddressScrollBar.cpp \
|
||||
widgets/R2AIWidget.cpp \
|
||||
widgets/R2McpWidget.cpp \
|
||||
common/SelectionHighlight.cpp \
|
||||
common/Decompiler.cpp \
|
||||
common/R2GhidraCmdDecompiler.cpp \
|
||||
common/R2JadxDecompiler.cpp \
|
||||
common/ShortcutKeys.cpp \
|
||||
common/ShortcutManager.cpp \
|
||||
dialogs/ShortcutKeysDialog.cpp \
|
||||
common/R2SourceDecompiler.cpp \
|
||||
common/R2pdcCmdDecompiler.cpp \
|
||||
common/R2DecaiDecompiler.cpp \
|
||||
common/R2AiDecompiler.cpp \
|
||||
common/R2AnotesDecompiler.cpp \
|
||||
common/R2HermesDecompiler.cpp \
|
||||
common/R2retdecDecompiler.cpp \
|
||||
menus/AddressableItemContextMenu.cpp \
|
||||
common/AddressableItemModel.cpp \
|
||||
common/NameListModel.cpp \
|
||||
widgets/NameListView.cpp \
|
||||
widgets/ListDockWidget.cpp \
|
||||
dialogs/MultitypeFileSaveDialog.cpp \
|
||||
widgets/BoolToggleDelegate.cpp \
|
||||
common/IOModesController.cpp \
|
||||
common/SettingsUpgrade.cpp \
|
||||
dialogs/LayoutManager.cpp \
|
||||
common/IaitoLayout.cpp \
|
||||
widgets/GraphHorizontalAdapter.cpp \
|
||||
@@ -473,38 +353,55 @@ SOURCES += \
|
||||
widgets/SimpleTextGraphView.cpp \
|
||||
widgets/R2GraphWidget.cpp \
|
||||
widgets/CallGraph.cpp \
|
||||
widgets/CalculatorWidget.cpp \
|
||||
widgets/SyscallsWidget.cpp \
|
||||
widgets/AddressableDockWidget.cpp \
|
||||
dialogs/preferences/AnalOptionsWidget.cpp \
|
||||
dialogs/preferences/KeyboardOptionsWidget.cpp \
|
||||
dialogs/settings/AnalOptionsWidget.cpp \
|
||||
dialogs/settings/ShortcutsOptionsWidget.cpp \
|
||||
common/DecompilerHighlighter.cpp \
|
||||
dialogs/PackageManagerDialog.cpp
|
||||
dialogs/PackageManagerDialog.cpp \
|
||||
dialogs/AssemblerDialog.cpp \
|
||||
dialogs/ScriptManagerDialog.cpp \
|
||||
dialogs/ScriptManagerWidget.cpp \
|
||||
widgets/ZoomWidget.cpp \
|
||||
common/DataAnalysis.cpp \
|
||||
common/TypeLayout.cpp \
|
||||
widgets/DataAnalysisView.cpp \
|
||||
widgets/DataAnalysisWidget.cpp
|
||||
|
||||
GRAPHVIZ_SOURCES = \
|
||||
widgets/GraphvizLayout.cpp
|
||||
|
||||
HEADERS += \
|
||||
common/ExceptionInfo.h \
|
||||
widgets/ExceptionBar.h \
|
||||
common/R2Shims.h \
|
||||
core/Iaito.h \
|
||||
core/IaitoCommon.h \
|
||||
core/IaitoDescriptions.h \
|
||||
dialogs/EditStringDialog.h \
|
||||
dialogs/DumpDialog.h \
|
||||
dialogs/WriteCommandsDialogs.h \
|
||||
widgets/ClickableSvgWidget.h \
|
||||
widgets/DisassemblerGraphView.h \
|
||||
widgets/OverviewView.h \
|
||||
common/PreviewTooltip.h \
|
||||
common/RichTextPainter.h \
|
||||
common/CachedFontMetrics.h \
|
||||
common/TypeScriptHighlighter.h \
|
||||
common/Helpers.h \
|
||||
common/ShortcutKeys.h \
|
||||
common/ShortcutManager.h \
|
||||
dialogs/ShortcutKeysDialog.h \
|
||||
common/TextEditDialog.h \
|
||||
common/Highlighter.h \
|
||||
common/MdHighlighter.h \
|
||||
common/Markdown.h \
|
||||
common/DirectionalComboBox.h \
|
||||
common/AnalTask.h \
|
||||
dialogs/AboutDialog.h \
|
||||
dialogs/preferences/AsmOptionsWidget.h \
|
||||
dialogs/AddressSpaceManagerDialog.h \
|
||||
dialogs/settings/AsmOptionsWidget.h \
|
||||
dialogs/CommentsDialog.h \
|
||||
dialogs/EditInstructionDialog.h \
|
||||
dialogs/FlagDialog.h \
|
||||
@@ -512,6 +409,9 @@ HEADERS += \
|
||||
dialogs/NativeDebugDialog.h \
|
||||
dialogs/XrefsDialog.h \
|
||||
core/MainWindow.h \
|
||||
core/DockManager.h \
|
||||
core/DockBackend.h \
|
||||
core/QtDockBackend.h \
|
||||
dialogs/InitialOptionsDialog.h \
|
||||
dialogs/NewFileDialog.h \
|
||||
widgets/CommentsWidget.h \
|
||||
@@ -522,6 +422,7 @@ HEADERS += \
|
||||
widgets/ExportsWidget.h \
|
||||
widgets/FlagsWidget.h \
|
||||
widgets/FunctionsWidget.h \
|
||||
widgets/GlobalVariablesWidget.h \
|
||||
widgets/ImportsWidget.h \
|
||||
widgets/Omnibar.h \
|
||||
widgets/RelocsWidget.h \
|
||||
@@ -530,39 +431,52 @@ HEADERS += \
|
||||
widgets/SegmentsWidget.h \
|
||||
widgets/StringsWidget.h \
|
||||
widgets/SymbolsWidget.h \
|
||||
widgets/XrefsWidget.h \
|
||||
widgets/RefsWidget.h \
|
||||
widgets/CodeInfoWidget.h \
|
||||
menus/DisassemblyContextMenu.h \
|
||||
menus/DecompilerContextMenu.h \
|
||||
menus/ContextMenuIcons.h \
|
||||
menus/ColorPickerMenu.h \
|
||||
menus/PinPickerMenu.h \
|
||||
widgets/DisassemblyWidget.h \
|
||||
widgets/HexdumpWidget.h \
|
||||
common/Configuration.h \
|
||||
common/theme/Theme.h \
|
||||
common/theme/IaitoStyle.h \
|
||||
common/Colors.h \
|
||||
dialogs/SaveProjectDialog.h \
|
||||
common/TempConfig.h \
|
||||
common/SvgIconEngine.h \
|
||||
common/SyntaxHighlighter.h \
|
||||
widgets/DecompilerWidget.h \
|
||||
widgets/DecompilerTextEdit.h \
|
||||
widgets/VisualNavbar.h \
|
||||
widgets/GraphView.h \
|
||||
dialogs/preferences/PreferencesDialog.h \
|
||||
dialogs/preferences/AppearanceOptionsWidget.h \
|
||||
dialogs/preferences/PreferenceCategory.h \
|
||||
dialogs/preferences/GraphOptionsWidget.h \
|
||||
dialogs/preferences/InitializationFileEditor.h \
|
||||
dialogs/settings/SettingsDialog.h \
|
||||
dialogs/settings/AppearanceOptionsWidget.h \
|
||||
dialogs/settings/SettingCategory.h \
|
||||
dialogs/settings/GraphOptionsWidget.h \
|
||||
dialogs/settings/InitializationFileEditor.h \
|
||||
widgets/QuickFilterView.h \
|
||||
widgets/ClassesWidget.h \
|
||||
widgets/ResourcesWidget.h \
|
||||
IaitoApplication.h \
|
||||
common/DeepLink.h \
|
||||
common/SamplesDB.h \
|
||||
widgets/VTablesWidget.h \
|
||||
widgets/TypesWidget.h \
|
||||
widgets/HeadersWidget.h \
|
||||
widgets/InterfacesWidget.h \
|
||||
widgets/SearchWidget.h \
|
||||
common/PythonAPI.h \
|
||||
dialogs/R2PluginsDialog.h \
|
||||
widgets/IaitoDockWidget.h \
|
||||
widgets/IaitoTreeWidget.h \
|
||||
widgets/MapsWidget.h \
|
||||
widgets/BinariesWidget.h \
|
||||
widgets/FilesWidget.h \
|
||||
widgets/FilesystemWidget.h \
|
||||
common/SourceLineReference.h \
|
||||
widgets/GraphWidget.h \
|
||||
widgets/OverviewWidget.h \
|
||||
common/JsonTreeItem.h \
|
||||
@@ -588,8 +502,10 @@ HEADERS += \
|
||||
dialogs/R2TaskDialog.h \
|
||||
widgets/DebugActions.h \
|
||||
widgets/MemoryMapWidget.h \
|
||||
dialogs/preferences/DebugOptionsWidget.h \
|
||||
dialogs/preferences/PluginsOptionsWidget.h \
|
||||
dialogs/settings/DebugOptionsWidget.h \
|
||||
dialogs/settings/WebserverOptionsWidget.h \
|
||||
dialogs/settings/SamplesOptionsWidget.h \
|
||||
dialogs/settings/PluginsOptionsWidget.h \
|
||||
widgets/BreakpointWidget.h \
|
||||
dialogs/BreakpointsDialog.h \
|
||||
dialogs/AttachProcDialog.h \
|
||||
@@ -598,10 +514,10 @@ HEADERS += \
|
||||
common/InitialOptions.h \
|
||||
dialogs/EditVariablesDialog.h \
|
||||
dialogs/EditFunctionDialog.h \
|
||||
dialogs/SwitchJumpTableDialog.h \
|
||||
widgets/IaitoTreeView.h \
|
||||
widgets/ComboQuickFilterView.h \
|
||||
dialogs/HexdumpRangeDialog.h \
|
||||
common/QtResImporter.h \
|
||||
common/IaitoSeekable.h \
|
||||
common/RefreshDeferrer.h \
|
||||
dialogs/WelcomeDialog.h \
|
||||
@@ -611,40 +527,48 @@ HEADERS += \
|
||||
common/CrashHandler.h \
|
||||
dialogs/TypesInteractionDialog.h \
|
||||
widgets/SdbWidget.h \
|
||||
common/PythonManager.h \
|
||||
plugins/PluginManager.h \
|
||||
common/BasicBlockHighlighter.h \
|
||||
common/BasicInstructionHighlighter.h \
|
||||
common/UpdateWorker.h \
|
||||
widgets/ColorPicker.h \
|
||||
common/ColorThemeWorker.h \
|
||||
widgets/ColorThemeComboBox.h \
|
||||
widgets/MemoryDockWidget.h \
|
||||
widgets/ColorThemeListView.h \
|
||||
dialogs/preferences/ColorThemeEditDialog.h \
|
||||
dialogs/preferences/KeyboardOptionsWidget.h \
|
||||
dialogs/settings/ColorThemeEditDialog.h \
|
||||
dialogs/settings/InterfaceThemeEditDialog.h \
|
||||
dialogs/settings/ShortcutsOptionsWidget.h \
|
||||
dialogs/LinkTypeDialog.h \
|
||||
common/BugReporting.h \
|
||||
common/HighDpiPixmap.h \
|
||||
widgets/GraphLayout.h \
|
||||
widgets/GraphGridLayout.h \
|
||||
widgets/HexWidget.h \
|
||||
widgets/HexPatWidget.h \
|
||||
common/SparseHexLayout.h \
|
||||
widgets/AddressScrollBar.h \
|
||||
widgets/R2AIWidget.h \
|
||||
widgets/R2McpWidget.h \
|
||||
common/SelectionHighlight.h \
|
||||
common/Decompiler.h \
|
||||
common/R2GhidraCmdDecompiler.h \
|
||||
common/R2JadxDecompiler.h \
|
||||
common/R2DecaiDecompiler.h \
|
||||
common/R2AiDecompiler.h \
|
||||
common/R2AnotesDecompiler.h \
|
||||
common/R2HermesDecompiler.h \
|
||||
common/R2pdcCmdDecompiler.h \
|
||||
common/R2SourceDecompiler.h \
|
||||
common/R2retdecDecompiler.h \
|
||||
menus/AddressableItemContextMenu.h \
|
||||
common/AddressableItemModel.h \
|
||||
common/NameListModel.h \
|
||||
widgets/NameListView.h \
|
||||
widgets/ListDockWidget.h \
|
||||
widgets/AddressableItemList.h \
|
||||
dialogs/MultitypeFileSaveDialog.h \
|
||||
widgets/BoolToggleDelegate.h \
|
||||
common/IOModesController.h \
|
||||
common/SettingsUpgrade.h \
|
||||
dialogs/LayoutManager.h \
|
||||
common/IaitoLayout.h \
|
||||
common/BinaryTrees.h \
|
||||
@@ -655,20 +579,34 @@ HEADERS += \
|
||||
widgets/SimpleTextGraphView.h \
|
||||
widgets/R2GraphWidget.h \
|
||||
widgets/CallGraph.h \
|
||||
widgets/CalculatorWidget.h \
|
||||
widgets/SyscallsWidget.h \
|
||||
widgets/AddressableDockWidget.h \
|
||||
dialogs/preferences/AnalOptionsWidget.h \
|
||||
dialogs/settings/AnalOptionsWidget.h \
|
||||
common/DecompilerHighlighter.h \
|
||||
dialogs/PackageManagerDialog.h
|
||||
common/CodeMetaRange.h \
|
||||
dialogs/PackageManagerDialog.h \
|
||||
dialogs/AssemblerDialog.h \
|
||||
dialogs/ScriptManagerDialog.h \
|
||||
dialogs/ScriptManagerWidget.h \
|
||||
widgets/ZoomWidget.h \
|
||||
common/DataAnalysis.h \
|
||||
common/TypeLayout.h \
|
||||
widgets/DataAnalysisView.h \
|
||||
widgets/DataAnalysisWidget.h
|
||||
|
||||
GRAPHVIZ_HEADERS = widgets/GraphvizLayout.h
|
||||
|
||||
FORMS += \
|
||||
widgets/GlobalVariablesWidget.ui \
|
||||
widgets/DataAnalysisWidget.ui \
|
||||
dialogs/AboutDialog.ui \
|
||||
dialogs/EditStringDialog.ui \
|
||||
dialogs/Base64EnDecodedWriteDialog.ui \
|
||||
dialogs/DumpDialog.ui \
|
||||
dialogs/DuplicateFromOffsetDialog.ui \
|
||||
dialogs/IncrementDecrementDialog.ui \
|
||||
dialogs/preferences/AsmOptionsWidget.ui \
|
||||
dialogs/settings/AsmOptionsWidget.ui \
|
||||
dialogs/CommentsDialog.ui \
|
||||
dialogs/EditInstructionDialog.ui \
|
||||
dialogs/FlagDialog.ui \
|
||||
@@ -685,12 +623,12 @@ FORMS += \
|
||||
widgets/FlagsWidget.ui \
|
||||
widgets/StringsWidget.ui \
|
||||
widgets/HexdumpWidget.ui \
|
||||
widgets/HexPatWidget.ui \
|
||||
dialogs/SaveProjectDialog.ui \
|
||||
dialogs/preferences/PreferencesDialog.ui \
|
||||
dialogs/preferences/AppearanceOptionsWidget.ui \
|
||||
dialogs/preferences/GraphOptionsWidget.ui \
|
||||
dialogs/preferences/InitializationFileEditor.ui \
|
||||
dialogs/preferences/KeyboardOptionsWidget.ui \
|
||||
dialogs/settings/SettingsDialog.ui \
|
||||
dialogs/settings/AppearanceOptionsWidget.ui \
|
||||
dialogs/settings/GraphOptionsWidget.ui \
|
||||
dialogs/settings/InitializationFileEditor.ui \
|
||||
widgets/QuickFilterView.ui \
|
||||
widgets/DecompilerWidget.ui \
|
||||
widgets/ClassesWidget.ui \
|
||||
@@ -708,7 +646,9 @@ FORMS += \
|
||||
widgets/ProcessesWidget.ui \
|
||||
widgets/BacktraceWidget.ui \
|
||||
dialogs/MapFileDialog.ui \
|
||||
dialogs/preferences/DebugOptionsWidget.ui \
|
||||
dialogs/settings/DebugOptionsWidget.ui \
|
||||
dialogs/settings/WebserverOptionsWidget.ui \
|
||||
dialogs/settings/SamplesOptionsWidget.ui \
|
||||
widgets/BreakpointWidget.ui \
|
||||
dialogs/BreakpointsDialog.ui \
|
||||
dialogs/AttachProcDialog.ui \
|
||||
@@ -716,7 +656,6 @@ FORMS += \
|
||||
dialogs/SetToDataDialog.ui \
|
||||
dialogs/EditVariablesDialog.ui \
|
||||
widgets/IaitoTreeView.ui \
|
||||
widgets/ComboQuickFilterView.ui \
|
||||
dialogs/HexdumpRangeDialog.ui \
|
||||
dialogs/WelcomeDialog.ui \
|
||||
dialogs/EditMethodDialog.ui \
|
||||
@@ -724,19 +663,16 @@ FORMS += \
|
||||
widgets/SdbWidget.ui \
|
||||
dialogs/LinkTypeDialog.ui \
|
||||
widgets/ColorPicker.ui \
|
||||
dialogs/preferences/ColorThemeEditDialog.ui \
|
||||
dialogs/settings/ColorThemeEditDialog.ui \
|
||||
widgets/ListDockWidget.ui \
|
||||
dialogs/LayoutManager.ui \
|
||||
widgets/R2GraphWidget.ui \
|
||||
widgets/CustomCommandWidget.ui \
|
||||
dialogs/preferences/AnalOptionsWidget.ui
|
||||
dialogs/settings/AnalOptionsWidget.ui
|
||||
|
||||
RESOURCES += \
|
||||
resources.qrc \
|
||||
themes/native/native.qrc \
|
||||
themes/qdarkstyle/dark.qrc \
|
||||
themes/midnight/midnight.qrc \
|
||||
themes/lightstyle/light.qrc
|
||||
themes/engine/engine.qrc
|
||||
|
||||
|
||||
DISTFILES += Iaito.astylerc
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
#include "IaitoApplication.h"
|
||||
#include "IaitoConfig.h"
|
||||
#include "R2AiDecompiler.h"
|
||||
#include "R2AnotesDecompiler.h"
|
||||
#include "R2DecaiDecompiler.h"
|
||||
#include "R2GhidraCmdDecompiler.h"
|
||||
#include "R2HermesDecompiler.h"
|
||||
#include "R2JadxDecompiler.h"
|
||||
#include "R2SourceDecompiler.h"
|
||||
#include "R2pdcCmdDecompiler.h"
|
||||
#include "R2retdecDecompiler.h"
|
||||
#include "common/CrashHandler.h"
|
||||
#include "common/Decompiler.h"
|
||||
#include "common/PythonManager.h"
|
||||
#include "common/DeepLink.h"
|
||||
#include "common/ResourcePaths.h"
|
||||
#include "common/SamplesDB.h"
|
||||
#include "common/ShortcutManager.h"
|
||||
#include "common/theme/IaitoStyle.h"
|
||||
#include "plugins/PluginManager.h"
|
||||
|
||||
#include <QApplication>
|
||||
@@ -17,6 +24,10 @@
|
||||
#include <QFileOpenEvent>
|
||||
#include <QMenu>
|
||||
#include <QMessageBox>
|
||||
#include <QProxyStyle>
|
||||
#include <QStyle>
|
||||
#include <QStyleHintReturn>
|
||||
#include <QStyleOption>
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
#include <QTextCodec>
|
||||
#endif
|
||||
@@ -28,9 +39,11 @@
|
||||
#include <QStandardPaths>
|
||||
#include <QStringList>
|
||||
#include <QTranslator>
|
||||
#include <QUrl>
|
||||
#ifdef Q_OS_WIN
|
||||
#include <QtNetwork/QtNetwork>
|
||||
#endif // Q_OS_WIN
|
||||
#include <cstring>
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
@@ -41,12 +54,12 @@
|
||||
static bool versionCheck()
|
||||
{
|
||||
// Check r2 version
|
||||
QString a = r_core_version(); // runtime library version
|
||||
QString b = "" R2_GITTAP; // compiled version
|
||||
QStringList la = a.split(".");
|
||||
QStringList lb = b.split(".");
|
||||
if (la.size() < 2 && lb.size() < 2) {
|
||||
R_LOG_WARN("Invalid version string somwhere");
|
||||
const QString a = r_core_version(); // runtime library version
|
||||
const QString b = "" R2_GITTAP; // compiled version
|
||||
const QStringList la = a.split(".");
|
||||
const QStringList lb = b.split(".");
|
||||
if (la.size() < 2 || lb.size() < 2) {
|
||||
R_LOG_WARN("Invalid version string somewhere");
|
||||
return false;
|
||||
}
|
||||
if (la.at(0) != lb.at(0)) {
|
||||
@@ -60,9 +73,35 @@ static bool versionCheck()
|
||||
return true;
|
||||
}
|
||||
|
||||
static QtMessageHandler g_previousMessageHandler = nullptr;
|
||||
|
||||
static void iaitoMessageHandler(QtMsgType type, const QMessageLogContext &ctx, const QString &msg)
|
||||
{
|
||||
// The category is on ctx.category, not inside msg. Drop qt.gui.imageio
|
||||
// entirely (libpng IDAT spam from PNGs shipped inside Qt itself) and any
|
||||
// stray "libpng warning:" text that might arrive without a category.
|
||||
const char *cat = ctx.category;
|
||||
if (cat && !strncmp(cat, "qt.gui.imageio", 14)) {
|
||||
return;
|
||||
}
|
||||
if (msg.contains(QLatin1String("libpng warning:"))) {
|
||||
return;
|
||||
}
|
||||
if (g_previousMessageHandler) {
|
||||
g_previousMessageHandler(type, ctx, msg);
|
||||
} else {
|
||||
QByteArray local = msg.toLocal8Bit();
|
||||
fprintf(stderr, "%s\n", local.constData());
|
||||
}
|
||||
}
|
||||
|
||||
IaitoApplication::IaitoApplication(int &argc, char **argv)
|
||||
: QApplication(argc, argv)
|
||||
, m_FileAlreadyDropped(false)
|
||||
{
|
||||
// Install message handler before widgets load to filter Qt's libpng spam.
|
||||
g_previousMessageHandler = qInstallMessageHandler(iaitoMessageHandler);
|
||||
|
||||
// Setup application information
|
||||
setApplicationVersion(IAITO_VERSION_FULL);
|
||||
#ifndef Q_OS_MACOS
|
||||
@@ -73,6 +112,8 @@ IaitoApplication::IaitoApplication(int &argc, char **argv)
|
||||
#endif
|
||||
setLayoutDirection(Qt::LeftToRight);
|
||||
|
||||
setStyle(new IaitoStyle());
|
||||
|
||||
// WARN!!! Put initialization code below this line. Code above this line is
|
||||
// mandatory to be run First
|
||||
#ifdef Q_OS_WIN
|
||||
@@ -99,6 +140,28 @@ IaitoApplication::IaitoApplication(int &argc, char **argv)
|
||||
if (ret == -1) {
|
||||
qWarning() << "Cannot load Incosolata-Regular font.";
|
||||
}
|
||||
ret = QFontDatabase::addApplicationFont(":/fonts/IBMPlexMono-Regular.ttf");
|
||||
if (ret == -1) {
|
||||
qWarning() << "Cannot load IBM Plex Mono Regular font.";
|
||||
}
|
||||
ret = QFontDatabase::addApplicationFont(":/fonts/IBMPlexMono-Bold.ttf");
|
||||
if (ret == -1) {
|
||||
qWarning() << "Cannot load IBM Plex Mono Bold font.";
|
||||
}
|
||||
#if 0
|
||||
ret = QFontDatabase::addApplicationFont(":/fonts/W95FA.otf");
|
||||
if (ret == -1) {
|
||||
qWarning() << "Cannot load W95FA font.";
|
||||
}
|
||||
ret = QFontDatabase::addApplicationFont(":/fonts/CourierPixel.otf");
|
||||
if (ret == -1) {
|
||||
qWarning() << "Cannot load Courier Pixel font.";
|
||||
}
|
||||
#endif
|
||||
ret = QFontDatabase::addApplicationFont(":/fonts/windows.ttf");
|
||||
if (ret == -1) {
|
||||
qWarning() << "Cannot load Windows font.";
|
||||
}
|
||||
|
||||
// Set QString codec to UTF-8
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
@@ -109,8 +172,7 @@ IaitoApplication::IaitoApplication(int &argc, char **argv)
|
||||
#endif
|
||||
#endif
|
||||
if (!parseCommandLineOptions()) {
|
||||
QCoreApplication::exit();
|
||||
// std::exit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!versionCheck()) {
|
||||
@@ -120,9 +182,11 @@ IaitoApplication::IaitoApplication(int &argc, char **argv)
|
||||
msg.setWindowTitle(QObject::tr("Version mismatch!"));
|
||||
QString localVersion = r_core_version();
|
||||
QString r2version = R2_GITTAP;
|
||||
msg.setText(QString(QObject::tr("The version used to compile Iaito (%1) does not match the "
|
||||
"binary version of radare2 (%2). This could result in "
|
||||
"unexpected behaviour. Are you sure you want to continue?"))
|
||||
msg.setText(QString(
|
||||
QObject::tr(
|
||||
"The version used to compile Iaito (%1) does not match the "
|
||||
"binary version of radare2 (%2). This could result in "
|
||||
"unexpected behaviour. Are you sure you want to continue?"))
|
||||
.arg(localVersion, r2version));
|
||||
if (msg.exec() == QMessageBox::No) {
|
||||
QCoreApplication::exit();
|
||||
@@ -130,14 +194,6 @@ IaitoApplication::IaitoApplication(int &argc, char **argv)
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef IAITO_ENABLE_PYTHON
|
||||
// Init python
|
||||
if (!clOptions.pythonHome.isEmpty()) {
|
||||
Python()->setPythonHome(clOptions.pythonHome);
|
||||
}
|
||||
Python()->initialize();
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
// Redefine r_sys_prefix() behaviour
|
||||
qputenv("R_ALT_SRC_DIR", "1");
|
||||
@@ -152,15 +208,20 @@ IaitoApplication::IaitoApplication(int &argc, char **argv)
|
||||
r2prefix.cd("Resources/radare2"); // Contents/Resources/radare2
|
||||
qputenv("R2_PREFIX", r2prefix.absolutePath().toLocal8Bit());
|
||||
|
||||
auto r2www = appdir; // Contents
|
||||
r2www.cd("Resources/radare2/share/radare2/last/www");
|
||||
qputenv("R2_WWWROOT", r2www.absolutePath().toLocal8Bit());
|
||||
|
||||
auto r2bin = appdir; // Contents
|
||||
r2bin.cd("Helpers"); // Contents/Helpers
|
||||
auto paths = QStringList(QString::fromLocal8Bit(qgetenv("PATH")));
|
||||
paths.prepend(r2bin.absolutePath());
|
||||
qputenv("PATH", paths.join(QLatin1Char(':')).toLocal8Bit());
|
||||
|
||||
// auto sleighHome = appdir; // Contents
|
||||
// sleighHome.cd("PlugIns/radare2/r2ghidra_sleigh"); // Contents/PlugIns/radare2/r2ghidra_sleigh
|
||||
// qputenv("SLEIGHHOME", sleighHome.absolutePath().toLocal8Bit());
|
||||
auto sleighHome = appdir; // Contents
|
||||
sleighHome.cd(
|
||||
"Resources/radare2/share/r2ghidra_sleigh"); // Contents/Resources/radare2/share/r2ghidra_sleigh
|
||||
qputenv("SLEIGHHOME", sleighHome.absolutePath().toLocal8Bit());
|
||||
|
||||
// Allow bundled radare2 and its plugins to save its data inside iaito app folders
|
||||
qputenv(
|
||||
@@ -179,27 +240,40 @@ IaitoApplication::IaitoApplication(int &argc, char **argv)
|
||||
Core()->setSettings();
|
||||
Config()->loadInitial();
|
||||
Core()->loadIaitoRC(0);
|
||||
ShortcutMgr()->migrateLegacyCommandKeys();
|
||||
|
||||
Config()->setOutputRedirectionEnabled(clOptions.outputRedirectionEnabled);
|
||||
|
||||
if (R2pdcCmdDecompiler::isAvailable()) {
|
||||
Core()->registerDecompiler(new R2pdcCmdDecompiler(Core()));
|
||||
}
|
||||
if (R2SourceDecompiler::isAvailable()) {
|
||||
Core()->registerDecompiler(new R2SourceDecompiler(Core()));
|
||||
}
|
||||
if (R2retdecDecompiler::isAvailable()) {
|
||||
Core()->registerDecompiler(new R2retdecDecompiler(Core()));
|
||||
}
|
||||
if (R2DecDecompiler::isAvailable()) {
|
||||
Core()->registerDecompiler(new R2DecDecompiler(Core()));
|
||||
}
|
||||
if (R2JadxDecompiler::isAvailable()) {
|
||||
Core()->registerDecompiler(new R2JadxDecompiler(Core()));
|
||||
}
|
||||
if (R2DecaiDecompiler::isAvailable()) {
|
||||
Core()->registerDecompiler(new R2DecaiDecompiler(Core()));
|
||||
}
|
||||
if (R2AiDecompiler::isAvailable()) {
|
||||
Core()->registerDecompiler(new R2AiDecompiler(Core()));
|
||||
}
|
||||
if (R2GhidraCmdDecompiler::isAvailable()) {
|
||||
Core()->registerDecompiler(new R2GhidraCmdDecompiler(Core()));
|
||||
}
|
||||
if (R2AnotesDecompiler::isAvailable()) {
|
||||
Core()->registerDecompiler(new R2AnotesDecompiler(Core()));
|
||||
}
|
||||
if (R2HermesDecompiler::isAvailable()) {
|
||||
Core()->registerDecompiler(new R2HermesDecompiler(Core()));
|
||||
}
|
||||
|
||||
#if IAITO_R2GHIDRA_STATIC
|
||||
Core()->registerDecompiler(new R2GhidraDecompiler(Core()));
|
||||
@@ -214,17 +288,14 @@ IaitoApplication::IaitoApplication(int &argc, char **argv)
|
||||
mainWindow = new MainWindow();
|
||||
installEventFilter(mainWindow);
|
||||
|
||||
// set up context menu shortcut display fix
|
||||
#if QT_VERSION_CHECK(5, 10, 0) < QT_VERSION
|
||||
setStyle(new IaitoProxyStyle());
|
||||
#endif // QT_VERSION_CHECK(5, 10, 0) < QT_VERSION
|
||||
|
||||
RCore *kore = iaitoPluginCore();
|
||||
if (kore) {
|
||||
mainWindow->openCurrentCore(clOptions.fileOpenOptions, false);
|
||||
} else if (!clOptions.deepLinkUrl.isEmpty()) {
|
||||
DeepLink::handle(mainWindow, clOptions.deepLinkUrl);
|
||||
} else if (clOptions.args.empty()) {
|
||||
// check if this is the first execution of Iaito in this computer
|
||||
// Note: the execution after the preferences been reset, will be
|
||||
// Note: the execution after the settings been reset, will be
|
||||
// considered as first-execution
|
||||
if (clOptions.showWelcomeDialog || Config()->isFirstExecution()) {
|
||||
mainWindow->displayWelcomeDialog();
|
||||
@@ -266,11 +337,11 @@ IaitoApplication::IaitoApplication(int &argc, char **argv)
|
||||
|
||||
IaitoApplication::~IaitoApplication()
|
||||
{
|
||||
Plugins()->destroyPlugins();
|
||||
delete mainWindow;
|
||||
#ifdef IAITO_ENABLE_PYTHON
|
||||
Python()->shutdown();
|
||||
#endif
|
||||
if (mainWindow) {
|
||||
SamplesDB::waitForPending();
|
||||
Plugins()->destroyPlugins();
|
||||
delete mainWindow;
|
||||
}
|
||||
}
|
||||
|
||||
void IaitoApplication::launchNewInstance(const QStringList &args)
|
||||
@@ -297,11 +368,16 @@ bool IaitoApplication::event(QEvent *e)
|
||||
}
|
||||
QFileOpenEvent *openEvent = static_cast<QFileOpenEvent *>(e);
|
||||
if (openEvent) {
|
||||
const QUrl url = openEvent->url();
|
||||
const bool isDeep = DeepLink::isDeepLink(url.toString());
|
||||
if (m_FileAlreadyDropped) {
|
||||
// We already dropped a file in macOS, let's spawn another
|
||||
// instance (Like the File -> Open)
|
||||
QString fileName = openEvent->file();
|
||||
launchNewInstance({fileName});
|
||||
launchNewInstance({isDeep ? url.toString() : openEvent->file()});
|
||||
} else if (isDeep) {
|
||||
m_FileAlreadyDropped = true;
|
||||
mainWindow->closeNewFileDialog();
|
||||
DeepLink::handle(mainWindow, url.toString());
|
||||
} else {
|
||||
QString fileName = openEvent->file();
|
||||
// eprintf ("FILE %s\n", fileName.toStdString().c_str());
|
||||
@@ -390,11 +466,16 @@ bool IaitoApplication::parseCommandLineOptions()
|
||||
cmd_parser.addVersionOption();
|
||||
cmd_parser.addPositionalArgument("filename", QObject::tr("Filename to open."));
|
||||
|
||||
QCommandLineOption environmentOption(
|
||||
"H", QObject::tr("List environment variables, or print the value of a selected variable."));
|
||||
cmd_parser.addOption(environmentOption);
|
||||
|
||||
QCommandLineOption analOption(
|
||||
{"A", "analysis"},
|
||||
QObject::tr("Automatically open file and optionally start analysis. "
|
||||
"Needs filename to be specified. May be a value between 0 and 3:"
|
||||
" 0 = no analysis, 1 = aa, 2 = aaa, 3 = aaaa (slow)"),
|
||||
QObject::tr(
|
||||
"Automatically open file and optionally start analysis. "
|
||||
"Needs filename to be specified. May be a value between 0 and 3:"
|
||||
" 0 = no analysis, 1 = aa, 2 = aaa, 3 = aaaa (slow)"),
|
||||
QObject::tr("level"));
|
||||
cmd_parser.addOption(analOption);
|
||||
|
||||
@@ -410,6 +491,10 @@ bool IaitoApplication::parseCommandLineOptions()
|
||||
QObject::tr("base address"));
|
||||
cmd_parser.addOption(baddrOption);
|
||||
|
||||
QCommandLineOption mapaddrOption(
|
||||
{"m", "map"}, QObject::tr("Map the file at a specific address"), QObject::tr("map address"));
|
||||
cmd_parser.addOption(mapaddrOption);
|
||||
|
||||
QCommandLineOption scriptOption("i", QObject::tr("Run script file"), QObject::tr("file"));
|
||||
cmd_parser.addOption(scriptOption);
|
||||
|
||||
@@ -420,18 +505,13 @@ bool IaitoApplication::parseCommandLineOptions()
|
||||
binCacheOption({"c", "bincache"}, QObject::tr("Patch relocs (enable bin.cache)"));
|
||||
cmd_parser.addOption(binCacheOption);
|
||||
|
||||
QCommandLineOption pythonHomeOption(
|
||||
"pythonhome",
|
||||
QObject::tr("PYTHONHOME to use for embedded python interpreter"),
|
||||
"PYTHONHOME");
|
||||
cmd_parser.addOption(pythonHomeOption);
|
||||
|
||||
QCommandLineOption disableRedirectOption(
|
||||
"no-output-redirect",
|
||||
QObject::tr("Disable output redirection."
|
||||
" Some of the output in console widget will not be visible."
|
||||
" Use this option when debuging a crash or freeze and output "
|
||||
" redirection is causing some messages to be lost."));
|
||||
QObject::tr(
|
||||
"Disable output redirection."
|
||||
" Some of the output in console widget will not be visible."
|
||||
" Use this option when debuging a crash or freeze and output "
|
||||
" redirection is causing some messages to be lost."));
|
||||
cmd_parser.addOption(disableRedirectOption);
|
||||
|
||||
QCommandLineOption disablePlugins("no-plugins", QObject::tr("Do not load plugins"));
|
||||
@@ -447,11 +527,35 @@ bool IaitoApplication::parseCommandLineOptions()
|
||||
QCommandLineOption welcomeOption("welcome", QObject::tr("Show welcome dialog."));
|
||||
cmd_parser.addOption(welcomeOption);
|
||||
|
||||
cmd_parser.process(*this);
|
||||
#ifdef IAITO_ENABLE_DEBUGGER
|
||||
QCommandLineOption debugOption(
|
||||
{"d", "debug"},
|
||||
QObject::tr(
|
||||
"Open the positional argument in debug mode. The argument may be a"
|
||||
" path to an executable to run or a numeric PID to attach to."));
|
||||
cmd_parser.addOption(debugOption);
|
||||
#endif
|
||||
|
||||
// process() exits without destroying QApplication for --version.
|
||||
const bool parsed = cmd_parser.parse(arguments());
|
||||
if (parsed && cmd_parser.isSet("version")) {
|
||||
printf("%s %s\n", qPrintable(applicationName()), qPrintable(applicationVersion()));
|
||||
exitCode = 0;
|
||||
return false;
|
||||
}
|
||||
if (!parsed || cmd_parser.isSet("help") || cmd_parser.isSet("help-all")) {
|
||||
cmd_parser.process(*this);
|
||||
}
|
||||
|
||||
IaitoCommandLineOptions opts;
|
||||
opts.args = cmd_parser.positionalArguments();
|
||||
|
||||
// An iaito:// deep link is passed as the positional argument by the OS URI
|
||||
// handler. Pull it out so it is not treated as a regular filename.
|
||||
if (!opts.args.isEmpty() && DeepLink::isDeepLink(opts.args.first())) {
|
||||
opts.deepLinkUrl = opts.args.takeFirst();
|
||||
}
|
||||
|
||||
if (cmd_parser.isSet(analOption)) {
|
||||
bool analLevelSpecified = false;
|
||||
int analLevel = cmd_parser.value(analOption).toInt(&analLevelSpecified);
|
||||
@@ -491,7 +595,6 @@ bool IaitoApplication::parseCommandLineOptions()
|
||||
return false;
|
||||
}
|
||||
|
||||
InitialOptions options;
|
||||
if (!opts.args.isEmpty()) {
|
||||
opts.fileOpenOptions.filename = opts.args[0];
|
||||
opts.fileOpenOptions.forceBinPlugin = cmd_parser.value(formatOption);
|
||||
@@ -499,7 +602,14 @@ bool IaitoApplication::parseCommandLineOptions()
|
||||
bool ok;
|
||||
RVA baddr = cmd_parser.value(baddrOption).toULongLong(&ok, 0);
|
||||
if (ok) {
|
||||
options.binLoadAddr = baddr;
|
||||
opts.fileOpenOptions.binLoadAddr = baddr;
|
||||
}
|
||||
}
|
||||
if (cmd_parser.isSet(mapaddrOption)) {
|
||||
bool ok;
|
||||
RVA mapaddr = cmd_parser.value(mapaddrOption).toULongLong(&ok, 0);
|
||||
if (ok) {
|
||||
opts.fileOpenOptions.mapAddr = mapaddr;
|
||||
}
|
||||
}
|
||||
switch (opts.analLevel) {
|
||||
@@ -521,10 +631,21 @@ bool IaitoApplication::parseCommandLineOptions()
|
||||
opts.fileOpenOptions.script = cmd_parser.value(scriptOption);
|
||||
|
||||
opts.fileOpenOptions.writeEnabled = cmd_parser.isSet(writeModeOption);
|
||||
}
|
||||
|
||||
if (cmd_parser.isSet(pythonHomeOption)) {
|
||||
opts.pythonHome = cmd_parser.value(pythonHomeOption);
|
||||
#ifdef IAITO_ENABLE_DEBUGGER
|
||||
if (cmd_parser.isSet(debugOption)) {
|
||||
// -d: open the target as a debug session. If the positional
|
||||
// argument is numeric, treat it as a PID to attach to; otherwise
|
||||
// treat it as a path to an executable to run under the debugger.
|
||||
const QString &target = opts.args[0];
|
||||
bool isPid = false;
|
||||
target.toLongLong(&isPid);
|
||||
if (isPid) {
|
||||
opts.fileOpenOptions.filename = QStringLiteral("dbg://%1").arg(target);
|
||||
}
|
||||
opts.fileOpenOptions.debug = true;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
opts.outputRedirectionEnabled = !cmd_parser.isSet(disableRedirectOption);
|
||||
@@ -549,18 +670,16 @@ bool IaitoApplication::parseCommandLineOptions()
|
||||
return true;
|
||||
}
|
||||
|
||||
void IaitoProxyStyle::polish(QWidget *widget)
|
||||
bool IaitoApplication::notify(QObject *receiver, QEvent *event)
|
||||
{
|
||||
QProxyStyle::polish(widget);
|
||||
#if QT_VERSION_CHECK(5, 10, 0) < QT_VERSION
|
||||
// HACK: This is the only way I've found to force Qt (5.10 and newer) to
|
||||
// display shortcuts in context menus on all platforms. It's ugly,
|
||||
// but it gets the job done.
|
||||
if (auto menu = qobject_cast<QMenu *>(widget)) {
|
||||
const auto &actions = menu->actions();
|
||||
for (auto action : actions) {
|
||||
action->setShortcutVisibleInContextMenu(true);
|
||||
if (event->type() == QEvent::Polish) {
|
||||
if (auto menu = qobject_cast<QMenu *>(receiver)) {
|
||||
for (auto action : menu->actions()) {
|
||||
action->setShortcutVisibleInContextMenu(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // QT_VERSION_CHECK(5, 10, 0) < QT_VERSION
|
||||
#endif
|
||||
return QApplication::notify(receiver, event);
|
||||
}
|
||||
@@ -4,7 +4,6 @@
|
||||
#include <QApplication>
|
||||
#include <QEvent>
|
||||
#include <QList>
|
||||
#include <QProxyStyle>
|
||||
|
||||
#include "core/MainWindow.h"
|
||||
|
||||
@@ -15,11 +14,11 @@ struct IaitoCommandLineOptions
|
||||
QStringList args;
|
||||
AutomaticAnalysisLevel analLevel = AutomaticAnalysisLevel::Ask;
|
||||
InitialOptions fileOpenOptions;
|
||||
QString pythonHome;
|
||||
bool outputRedirectionEnabled = true;
|
||||
bool enableIaitoPlugins = true;
|
||||
bool enableR2Plugins = true;
|
||||
bool showWelcomeDialog = false;
|
||||
QString deepLinkUrl;
|
||||
};
|
||||
|
||||
class IaitoApplication : public QApplication
|
||||
@@ -31,11 +30,13 @@ public:
|
||||
~IaitoApplication();
|
||||
|
||||
MainWindow *getMainWindow() { return mainWindow; }
|
||||
int getExitCode() const { return exitCode; }
|
||||
|
||||
void launchNewInstance(const QStringList &args = {});
|
||||
|
||||
protected:
|
||||
bool event(QEvent *e);
|
||||
bool event(QEvent *e) override;
|
||||
bool notify(QObject *receiver, QEvent *event) override;
|
||||
|
||||
private:
|
||||
/**
|
||||
@@ -51,22 +52,9 @@ private:
|
||||
|
||||
private:
|
||||
bool m_FileAlreadyDropped;
|
||||
MainWindow *mainWindow;
|
||||
MainWindow *mainWindow = nullptr;
|
||||
int exitCode = 1;
|
||||
IaitoCommandLineOptions clOptions;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief IaitoProxyStyle is used to force shortcuts displaying in context menu
|
||||
*/
|
||||
class IaitoProxyStyle : public QProxyStyle
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
/**
|
||||
* @brief it is enough to get notification about QMenu polishing to force
|
||||
* shortcut displaying
|
||||
*/
|
||||
void polish(QWidget *widget) override;
|
||||
};
|
||||
|
||||
#endif // IAITOAPPLICATION_H
|
||||
@@ -2,14 +2,78 @@
|
||||
#include "IaitoApplication.h"
|
||||
#include "IaitoConfig.h"
|
||||
#include "common/CrashHandler.h"
|
||||
#include "common/SettingsUpgrade.h"
|
||||
#include "common/UpdateWorker.h"
|
||||
#include "core/MainWindow.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <QDir>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
static void connectToConsole();
|
||||
#endif
|
||||
|
||||
static bool printEnvironmentVariables(int argc, char *argv[])
|
||||
{
|
||||
for (int i = 1; i < argc; i++) {
|
||||
if (!std::strcmp(argv[i], "--")) {
|
||||
return false;
|
||||
}
|
||||
if (std::strncmp(argv[i], "-H", 2)) {
|
||||
continue;
|
||||
}
|
||||
const char *selectedVariable = nullptr;
|
||||
if (argv[i][2]) {
|
||||
selectedVariable = argv[i] + 2;
|
||||
} else if (i + 1 < argc && argv[i + 1][0] != '-') {
|
||||
selectedVariable = argv[i + 1];
|
||||
}
|
||||
#ifdef Q_OS_WIN
|
||||
connectToConsole();
|
||||
#endif
|
||||
QByteArray extraPluginDirs = IAITO_EXTRA_PLUGIN_DIRS;
|
||||
const QByteArray environmentPluginDirs = qgetenv("IAITO_EXTRA_PLUGIN_DIRS");
|
||||
if (!environmentPluginDirs.isEmpty()) {
|
||||
if (!extraPluginDirs.isEmpty()) {
|
||||
extraPluginDirs.append(QDir::listSeparator().toLatin1());
|
||||
}
|
||||
extraPluginDirs.append(environmentPluginDirs);
|
||||
}
|
||||
struct EnvironmentVariable
|
||||
{
|
||||
const char *name;
|
||||
QByteArray value;
|
||||
};
|
||||
const EnvironmentVariable variables[] = {
|
||||
{"IAITO_VERSION", IAITO_VERSION_FULL},
|
||||
{"IAITO_VERSION_MAJOR", QByteArray::number(IAITO_VERSION_MAJOR)},
|
||||
{"IAITO_VERSION_MINOR", QByteArray::number(IAITO_VERSION_MINOR)},
|
||||
{"IAITO_VERSION_PATCH", QByteArray::number(IAITO_VERSION_PATCH)},
|
||||
{"IAITO_EXTRA_PLUGIN_DIRS", extraPluginDirs},
|
||||
{"R2_DEBUG", qgetenv("R2_DEBUG")},
|
||||
{"R2_NOPLUGINS", qgetenv("R2_NOPLUGINS")},
|
||||
};
|
||||
for (const auto &variable : variables) {
|
||||
const char *shortName = std::strchr(variable.name, '_');
|
||||
shortName = shortName ? shortName + 1 : variable.name;
|
||||
if (selectedVariable && std::strcmp(selectedVariable, variable.name)
|
||||
&& std::strcmp(selectedVariable, shortName)) {
|
||||
continue;
|
||||
}
|
||||
if (selectedVariable) {
|
||||
std::fprintf(stdout, "%s\n", variable.value.constData());
|
||||
} else {
|
||||
std::fprintf(stdout, "%s=%s\n", variable.name, variable.value.constData());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Attempt to connect to a parent console and configure outputs.
|
||||
*
|
||||
@@ -54,6 +118,10 @@ static void connectToConsole()
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
if (printEnvironmentVariables(argc, argv)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (argc >= 3 && QString::fromLocal8Bit(argv[1]) == "--start-crash-handler") {
|
||||
QApplication app(argc, argv);
|
||||
QString dumpLocation = QString::fromLocal8Bit(argv[2]);
|
||||
@@ -75,8 +143,6 @@ int main(int argc, char *argv[])
|
||||
QCoreApplication::setApplicationName("iaito");
|
||||
QGuiApplication::setDesktopFileName("org.radare.iaito");
|
||||
|
||||
Iaito::initializeSettings();
|
||||
|
||||
QCoreApplication::setAttribute(
|
||||
Qt::AA_ShareOpenGLContexts); // needed for QtWebEngine inside Plugins
|
||||
#ifdef Q_OS_WIN
|
||||
@@ -88,24 +154,10 @@ int main(int argc, char *argv[])
|
||||
#endif
|
||||
|
||||
IaitoApplication a(argc, argv);
|
||||
|
||||
Iaito::migrateThemes();
|
||||
|
||||
if (Config()->getAutoUpdateEnabled()) {
|
||||
#if IAITO_UPDATE_WORKER_AVAILABLE
|
||||
UpdateWorker *updateWorker = new UpdateWorker;
|
||||
QObject::connect(
|
||||
updateWorker,
|
||||
&UpdateWorker::checkComplete,
|
||||
[=](const QVersionNumber &version, const QString &error) {
|
||||
if (error.isEmpty() && version > UpdateWorker::currentVersionNumber()) {
|
||||
updateWorker->showUpdateDialog(true);
|
||||
}
|
||||
updateWorker->deleteLater();
|
||||
});
|
||||
updateWorker->checkCurrentVersion(7000);
|
||||
#endif
|
||||
if (!a.getMainWindow()) {
|
||||
return a.getExitCode();
|
||||
}
|
||||
|
||||
return a.exec();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
#ifndef CUSTOM_ASSERT_H
|
||||
#define CUSTOM_ASSERT_H 1
|
||||
|
||||
#define assert(x) \
|
||||
if ((x)) { \
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,12 +0,0 @@
|
||||
|
||||
# 😠 https://github.com/mesonbuild/meson/issues/2320
|
||||
|
||||
bindings_src_files = run_command([py3_exe,
|
||||
join_paths(meson.current_source_dir(), '../src_list.py'),
|
||||
'meson']).stdout().split(';')
|
||||
|
||||
bindings_target = custom_target('bindings',
|
||||
input: bindings_txt,
|
||||
depend_files: [bindings_h, bindings_xml],
|
||||
output: bindings_src_files,
|
||||
command: [shiboken_exe, '--project-file=@INPUT@'])
|
||||
@@ -1,14 +0,0 @@
|
||||
|
||||
#ifndef IAITO_BINDINGS_H
|
||||
#define IAITO_BINDINGS_H
|
||||
|
||||
// #define QT_ANNOTATE_ACCESS_SPECIFIER(a) __attribute__((annotate(#a)))
|
||||
|
||||
#include "../common/Configuration.h"
|
||||
#include "../core/Iaito.h"
|
||||
#include "../core/MainWindow.h"
|
||||
#include "../menus/AddressableItemContextMenu.h"
|
||||
#include "../plugins/IaitoPlugin.h"
|
||||
#include "../widgets/IaitoDockWidget.h"
|
||||
|
||||
#endif // IAITO_BINDINGS_H
|
||||
@@ -1,17 +0,0 @@
|
||||
[generator-project]
|
||||
|
||||
generator-set = shiboken
|
||||
|
||||
header-file = $${BINDINGS_SRC_DIR}/bindings.h
|
||||
typesystem-file = $${BINDINGS_SRC_DIR}/bindings.xml
|
||||
|
||||
output-directory = $${BINDINGS_BUILD_DIR}
|
||||
|
||||
include-path = $${BINDINGS_INCLUDE_DIRS}
|
||||
|
||||
typesystem-paths = $${PYSIDE_TYPESYSTEMS}
|
||||
|
||||
enable-parent-ctor-heuristic
|
||||
enable-pyside-extensions
|
||||
enable-return-value-heuristic
|
||||
use-isnull-as-nb_nonzero
|
||||
@@ -1,71 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<typesystem package="IaitoBindings">
|
||||
<load-typesystem name="typesystem_core.xml" generate="no" />
|
||||
<load-typesystem name="typesystem_gui.xml" generate="no" />
|
||||
<load-typesystem name="typesystem_widgets.xml" generate="no" />
|
||||
|
||||
<primitive-type name="bool"/>
|
||||
|
||||
<object-type name="IaitoCore">
|
||||
</object-type>
|
||||
<object-type name="Configuration" />
|
||||
<object-type name="MainWindow" >
|
||||
<enum-type name="MenuType" />
|
||||
<enum-type name="ContextMenuType" />
|
||||
</object-type>
|
||||
<object-type name="BasicBlockHighlighter" />
|
||||
<object-type name="AddressableItemContextMenu" />
|
||||
<object-type name="IaitoDockWidget" />
|
||||
|
||||
<template name="plugin_meta_get">
|
||||
SbkObject *wrapper = Shiboken::BindingManager::instance().retrieveWrapper(this);
|
||||
PyObject *classObject = PyObject_GetAttrString(reinterpret_cast<PyObject *>(wrapper), "__class__");
|
||||
if (!classObject) {
|
||||
PyErr_Print();
|
||||
return QString();
|
||||
}
|
||||
PyObject *pyResult = PyObject_GetAttrString(classObject, "VAR_NAME");
|
||||
if (!pyResult) {
|
||||
PyErr_Print();
|
||||
return QString();
|
||||
}
|
||||
PythonToCppFunc pythonToCpp = Shiboken::Conversions::isPythonToCppConvertible(SbkPySide2_QtCoreTypeConverters[SBK_QSTRING_IDX], pyResult);
|
||||
if (!pythonToCpp) {
|
||||
Shiboken::warning(PyExc_RuntimeWarning, 2, "Invalid return value for plugin metadata VAR_NAME, expected %s, got %s.", "QString", Py_TYPE(pyResult)->tp_name);
|
||||
return ::QString();
|
||||
}
|
||||
QString cppResult;
|
||||
pythonToCpp(pyResult, &cppResult);
|
||||
return cppResult;
|
||||
</template>
|
||||
<object-type name="IaitoPlugin">
|
||||
<modify-function signature="getName()const">
|
||||
<inject-code class="shell" position="beginning">
|
||||
<insert-template name="plugin_meta_get">
|
||||
<replace from="VAR_NAME" to="name" />
|
||||
</insert-template>
|
||||
</inject-code>
|
||||
</modify-function>
|
||||
<modify-function signature="getAuthor()const">
|
||||
<inject-code class="shell" position="beginning">
|
||||
<insert-template name="plugin_meta_get">
|
||||
<replace from="VAR_NAME" to="author" />
|
||||
</insert-template>
|
||||
</inject-code>
|
||||
</modify-function>
|
||||
<modify-function signature="getDescription()const">
|
||||
<inject-code class="shell" position="beginning">
|
||||
<insert-template name="plugin_meta_get">
|
||||
<replace from="VAR_NAME" to="description" />
|
||||
</insert-template>
|
||||
</inject-code>
|
||||
</modify-function>
|
||||
<modify-function signature="getVersion()const">
|
||||
<inject-code class="shell" position="beginning">
|
||||
<insert-template name="plugin_meta_get">
|
||||
<replace from="VAR_NAME" to="version" />
|
||||
</insert-template>
|
||||
</inject-code>
|
||||
</modify-function>
|
||||
</object-type>
|
||||
</typesystem>
|
||||
@@ -1,20 +0,0 @@
|
||||
|
||||
bindings_xml = configure_file(input: 'bindings.xml',
|
||||
output: 'bindings.xml',
|
||||
copy: true)
|
||||
|
||||
conf_json = '''{"BINDINGS_SRC_DIR":"@0@",
|
||||
"BINDINGS_BUILD_DIR":"@1@",
|
||||
"BINDINGS_INCLUDE_DIRS":"@2@",
|
||||
"PYSIDE_TYPESYSTEMS":"@3@"}'''.format(
|
||||
meson.current_source_dir(),
|
||||
meson.current_build_dir(),
|
||||
':'.join(bindings_generate_inc),
|
||||
pyside_dep.get_pkgconfig_variable('typesystemdir'))
|
||||
bindings_txt = configure_file(input: 'bindings.txt.in',
|
||||
output: 'bindings.txt',
|
||||
command: configure_qmake_cmd + ['@INPUT@', '@OUTPUT0@', conf_json])
|
||||
|
||||
bindings_h = files('bindings.h')
|
||||
|
||||
subdir('IaitoBindings')
|
||||
@@ -1,52 +0,0 @@
|
||||
#!/bin/env python3
|
||||
|
||||
import os
|
||||
import xml.etree.ElementTree as et
|
||||
import sys
|
||||
|
||||
|
||||
script_path = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
|
||||
def get_cpp_files_gen(args, include_package=True):
|
||||
ts_tree = et.parse(os.path.join(script_path, "bindings.xml"))
|
||||
ts_root = ts_tree.getroot()
|
||||
|
||||
package = ts_root.attrib["package"]
|
||||
|
||||
type_tags = ["object-type", "value-type"]
|
||||
types = [child.attrib["name"] for child in ts_root if child.tag in type_tags]
|
||||
|
||||
cpp_files_gen = [f"{package.lower()}_module_wrapper.cpp"]
|
||||
cpp_files_gen.extend([f"{typename.lower()}_wrapper.cpp" for typename in types])
|
||||
|
||||
if include_package:
|
||||
cpp_files_gen = [os.path.join(package, f) for f in cpp_files_gen]
|
||||
|
||||
if len(args) > 0:
|
||||
cpp_files_gen = [os.path.join(args[0], f) for f in cpp_files_gen]
|
||||
|
||||
return cpp_files_gen
|
||||
|
||||
|
||||
def cmd_cmake(args):
|
||||
files = get_cpp_files_gen(args)
|
||||
if sys.platform == "win32":
|
||||
files = map(lambda x: x.replace("\\", "/"), files)
|
||||
sys.stdout.write(";".join(files))
|
||||
|
||||
|
||||
def cmd_qmake(args):
|
||||
sys.stdout.write("\n".join(get_cpp_files_gen(args)) + "\n")
|
||||
|
||||
|
||||
def cmd_meson(args):
|
||||
sys.stdout.write(";".join(get_cpp_files_gen(args, include_package=False)))
|
||||
|
||||
|
||||
cmds = {"cmake": cmd_cmake, "qmake": cmd_qmake, "meson": cmd_meson}
|
||||
|
||||
if len(sys.argv) < 2 or sys.argv[1] not in cmds:
|
||||
print(f"""usage: {sys.argv[0]} [{"/".join(cmds.keys())}] [base path]""")
|
||||
exit(1)
|
||||
cmds[sys.argv[1]](sys.argv[2:])
|
||||
@@ -12,26 +12,68 @@ AddressableFilterProxyModel::AddressableFilterProxyModel(
|
||||
: AddressableItemModel<QSortFilterProxyModel>(parent)
|
||||
{
|
||||
setSourceModel(sourceModel);
|
||||
addressableSourceModel = sourceModel;
|
||||
}
|
||||
|
||||
RVA AddressableFilterProxyModel::address(const QModelIndex &index) const
|
||||
{
|
||||
return addressableSourceModel->address(this->mapToSource(index));
|
||||
const auto source = mapToSource(index);
|
||||
return addressableSourceModel && source.isValid() ? addressableSourceModel->address(source)
|
||||
: RVA_INVALID;
|
||||
}
|
||||
|
||||
QString AddressableFilterProxyModel::name(const QModelIndex &index) const
|
||||
{
|
||||
return addressableSourceModel->name(this->mapToSource(index));
|
||||
const auto source = mapToSource(index);
|
||||
return addressableSourceModel && source.isValid() ? addressableSourceModel->name(source)
|
||||
: QString();
|
||||
}
|
||||
|
||||
void AddressableFilterProxyModel::setSourceModel(QAbstractItemModel *)
|
||||
{
|
||||
throw new std::runtime_error("Not supported");
|
||||
throw std::runtime_error("Not supported");
|
||||
}
|
||||
|
||||
void AddressableFilterProxyModel::setSourceModel(AddressableItemModelI *sourceModel)
|
||||
{
|
||||
ParentClass::setSourceModel(sourceModel->asItemModel());
|
||||
addressableSourceModel = sourceModel;
|
||||
ParentClass::setSourceModel(sourceModel ? sourceModel->asItemModel() : nullptr);
|
||||
}
|
||||
|
||||
void AddressableFilterProxyModel::setTypeFilterColumn(int column)
|
||||
{
|
||||
if (typeColumn != column) {
|
||||
emit filterAboutToChange();
|
||||
typeColumn = column;
|
||||
invalidateFilter();
|
||||
emit filterChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void AddressableFilterProxyModel::setTypeFilter(const QVariant &type)
|
||||
{
|
||||
if (selectedType != type) {
|
||||
emit filterAboutToChange();
|
||||
selectedType = type;
|
||||
invalidateFilter();
|
||||
emit filterChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void AddressableFilterProxyModel::setFilterWildcard(const QString &pattern)
|
||||
{
|
||||
emit filterAboutToChange();
|
||||
ParentClass::setFilterWildcard(pattern);
|
||||
emit filterChanged();
|
||||
}
|
||||
|
||||
bool AddressableFilterProxyModel::filterAcceptsType(int row, const QModelIndex &parent) const
|
||||
{
|
||||
return typeColumn < 0 || !selectedType.isValid()
|
||||
|| sourceModel()->index(row, typeColumn, parent).data().toString()
|
||||
== selectedType.toString();
|
||||
}
|
||||
|
||||
bool AddressableFilterProxyModel::filterAcceptsRow(int row, const QModelIndex &parent) const
|
||||
{
|
||||
return filterAcceptsType(row, parent) && ParentClass::filterAcceptsRow(row, parent);
|
||||
}
|
||||
@@ -43,6 +43,8 @@ public:
|
||||
|
||||
class IAITO_EXPORT AddressableFilterProxyModel : public AddressableItemModel<QSortFilterProxyModel>
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
using ParentClass = AddressableItemModel<QSortFilterProxyModel>;
|
||||
|
||||
public:
|
||||
@@ -51,10 +53,26 @@ public:
|
||||
RVA address(const QModelIndex &index) const override;
|
||||
QString name(const QModelIndex &) const override;
|
||||
void setSourceModel(AddressableItemModelI *sourceModel);
|
||||
void setTypeFilterColumn(int column);
|
||||
void setTypeFilter(const QVariant &type);
|
||||
QVariant typeFilter() const { return selectedType; }
|
||||
|
||||
public slots:
|
||||
void setFilterWildcard(const QString &pattern);
|
||||
|
||||
signals:
|
||||
void filterAboutToChange();
|
||||
void filterChanged();
|
||||
|
||||
protected:
|
||||
bool filterAcceptsRow(int row, const QModelIndex &parent) const override;
|
||||
bool filterAcceptsType(int row, const QModelIndex &parent) const;
|
||||
|
||||
private:
|
||||
void setSourceModel(QAbstractItemModel *sourceModel) override; // Don't use this directly
|
||||
AddressableItemModelI *addressableSourceModel;
|
||||
AddressableItemModelI *addressableSourceModel = nullptr;
|
||||
int typeColumn = -1;
|
||||
QVariant selectedType;
|
||||
};
|
||||
|
||||
#endif // ADDRESSABLEITEMMODEL_H
|
||||
@@ -15,12 +15,7 @@ AnalTask::~AnalTask() {}
|
||||
void AnalTask::interrupt()
|
||||
{
|
||||
AsyncTask::interrupt();
|
||||
#if R2_VERSION_NUMBER >= 50909
|
||||
RCore *core = Core()->core_;
|
||||
core->cons->context->breaked = true;
|
||||
#else
|
||||
r_cons_singleton()->context->breaked = true;
|
||||
#endif
|
||||
Core()->setConsBreaked();
|
||||
}
|
||||
|
||||
QString AnalTask::getTitle()
|
||||
@@ -116,7 +111,6 @@ void AnalTask::runTask()
|
||||
if (isInterrupted()) {
|
||||
return;
|
||||
}
|
||||
// log(cmd.description);
|
||||
log(cmd.command + " : " + cmd.description);
|
||||
// use cmd instead of cmdRaw because commands can be unexpected
|
||||
Core()->cmd(cmd.command);
|
||||
|
||||
@@ -14,6 +14,11 @@ BasicBlockHighlighter::~BasicBlockHighlighter()
|
||||
*/
|
||||
void BasicBlockHighlighter::highlight(RVA address, const QColor &color)
|
||||
{
|
||||
auto it = bbMap.find(address);
|
||||
if (it != bbMap.end()) {
|
||||
delete it->second;
|
||||
bbMap.erase(it);
|
||||
}
|
||||
BasicBlock *block = new BasicBlock;
|
||||
block->address = address;
|
||||
block->color = color;
|
||||
@@ -25,7 +30,11 @@ void BasicBlockHighlighter::highlight(RVA address, const QColor &color)
|
||||
*/
|
||||
void BasicBlockHighlighter::clear(RVA address)
|
||||
{
|
||||
bbMap.erase(address);
|
||||
auto it = bbMap.find(address);
|
||||
if (it != bbMap.end()) {
|
||||
delete it->second;
|
||||
bbMap.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,7 +8,7 @@ class BasicBlockHighlighter;
|
||||
|
||||
struct BasicBlock
|
||||
{
|
||||
RVA address;
|
||||
RVA address = 0;
|
||||
QColor color;
|
||||
};
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
|
||||
struct BasicInstruction
|
||||
{
|
||||
RVA address;
|
||||
RVA size;
|
||||
RVA address = 0;
|
||||
RVA size = 0;
|
||||
QColor color;
|
||||
};
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ void openIssue()
|
||||
format = coreObj["format"].toString();
|
||||
arch = binObj["arch"].toString();
|
||||
if (!binObj["type"].isUndefined()) {
|
||||
type = coreObj["type"].toString();
|
||||
type = binObj["type"].toString();
|
||||
} else {
|
||||
type = "N/A";
|
||||
}
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
|
||||
#include "common/Metrics.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <QFont>
|
||||
#include <QFontMetrics>
|
||||
#include <QObject>
|
||||
@@ -13,25 +16,45 @@ class CachedFontMetrics
|
||||
public:
|
||||
explicit CachedFontMetrics(const QFont &font)
|
||||
: mFontMetrics(font)
|
||||
, mAsciiWidths{}
|
||||
{
|
||||
memset(mWidths, 0, sizeof(mWidths));
|
||||
mHeight = mFontMetrics.height();
|
||||
}
|
||||
|
||||
static std::shared_ptr<CachedFontMetrics<T>> forFont(const QFont &font)
|
||||
{
|
||||
static std::unordered_map<std::string, std::weak_ptr<CachedFontMetrics<T>>> cache;
|
||||
const std::string key = font.key().toStdString();
|
||||
auto it = cache.find(key);
|
||||
if (it != cache.end()) {
|
||||
if (auto sp = it->second.lock()) {
|
||||
return sp;
|
||||
}
|
||||
}
|
||||
auto sp = std::make_shared<CachedFontMetrics<T>>(font);
|
||||
cache[key] = sp;
|
||||
return sp;
|
||||
}
|
||||
|
||||
T width(const QChar &ch)
|
||||
{
|
||||
// return mFontMetrics.width(ch);
|
||||
auto unicode = ch.unicode();
|
||||
if (unicode >= 0xD800) {
|
||||
if (unicode >= 0xE000)
|
||||
unicode -= 0xE000 - 0xD800;
|
||||
else
|
||||
// is lonely surrogate
|
||||
return fetchWidth(ch);
|
||||
if (unicode < kAsciiCacheSize) {
|
||||
if (!mAsciiWidths[unicode]) {
|
||||
mAsciiWidths[unicode] = fetchWidth(ch);
|
||||
}
|
||||
return mAsciiWidths[unicode];
|
||||
}
|
||||
if (!mWidths[unicode])
|
||||
return mWidths[unicode] = fetchWidth(ch);
|
||||
return mWidths[unicode];
|
||||
if (unicode >= 0xD800 && unicode < 0xE000) {
|
||||
return fetchWidth(ch);
|
||||
}
|
||||
auto it = mWidths.find(unicode);
|
||||
if (it != mWidths.end()) {
|
||||
return it->second;
|
||||
}
|
||||
T w = fetchWidth(ch);
|
||||
mWidths.emplace(unicode, w);
|
||||
return w;
|
||||
}
|
||||
|
||||
T width(const QString &text)
|
||||
@@ -75,8 +98,11 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr int kAsciiCacheSize = 128;
|
||||
|
||||
typename Metrics<T>::FontMetrics mFontMetrics;
|
||||
T mWidths[0x10000 - 0xE000 + 0xD800];
|
||||
T mAsciiWidths[kAsciiCacheSize];
|
||||
std::unordered_map<uint16_t, T> mWidths;
|
||||
T mHeight;
|
||||
|
||||
T fetchWidth(QChar c)
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
#ifndef CODEMETA_RANGE_H
|
||||
#define CODEMETA_RANGE_H
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QJsonValue>
|
||||
#include <QString>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <optional>
|
||||
|
||||
struct CodeMetaRange
|
||||
{
|
||||
size_t start = 0;
|
||||
size_t end = 0;
|
||||
size_t length = 0;
|
||||
};
|
||||
|
||||
inline std::optional<CodeMetaRange> codeMetaRangeFromJson(const QJsonObject &obj, size_t codeLength)
|
||||
{
|
||||
int start = obj["start"].toInt(-1);
|
||||
int end = obj["end"].toInt(-1);
|
||||
if (start < 0 || end < start) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
size_t rangeStart = static_cast<size_t>(start);
|
||||
size_t rangeEnd = std::min(static_cast<size_t>(end), codeLength);
|
||||
if (rangeStart >= codeLength || rangeEnd <= rangeStart) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return CodeMetaRange{rangeStart, rangeEnd, rangeEnd - rangeStart};
|
||||
}
|
||||
|
||||
inline std::optional<CodeMetaRange> codeMetaOffsetRangeFromJson(
|
||||
const QJsonObject &obj, const QString &code)
|
||||
{
|
||||
int start = obj["start"].toInt(-1);
|
||||
int end = obj["end"].toInt(start);
|
||||
if (start < 0 || end < start || start >= code.size()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (end == start) {
|
||||
int lineStart = code.lastIndexOf(QLatin1Char('\n'), start - 1);
|
||||
lineStart = (lineStart < 0) ? 0 : lineStart + 1;
|
||||
int lineEnd = code.indexOf(QLatin1Char('\n'), start);
|
||||
if (lineEnd < 0) {
|
||||
lineEnd = code.size();
|
||||
}
|
||||
if (lineEnd <= lineStart) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return CodeMetaRange{
|
||||
static_cast<size_t>(lineStart),
|
||||
static_cast<size_t>(lineEnd),
|
||||
static_cast<size_t>(lineEnd - lineStart)};
|
||||
}
|
||||
|
||||
size_t rangeStart = static_cast<size_t>(start);
|
||||
size_t rangeEnd = std::min(static_cast<size_t>(end), static_cast<size_t>(code.size()));
|
||||
if (rangeEnd <= rangeStart) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return CodeMetaRange{rangeStart, rangeEnd, rangeEnd - rangeStart};
|
||||
}
|
||||
|
||||
inline std::optional<qulonglong> codeMetaAddressFromJson(const QJsonValue &value)
|
||||
{
|
||||
bool ok = false;
|
||||
qulonglong address = value.toVariant().toULongLong(&ok);
|
||||
if (ok) {
|
||||
return address;
|
||||
}
|
||||
|
||||
QString text = value.toString().trimmed();
|
||||
if (text.isEmpty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
address = text.toULongLong(&ok, 0);
|
||||
if (!ok && text.startsWith(QLatin1String("0x"), Qt::CaseInsensitive)) {
|
||||
address = text.mid(2).toULongLong(&ok, 16);
|
||||
}
|
||||
if (!ok) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return address;
|
||||
}
|
||||
|
||||
inline std::optional<CodeMetaRange> intersectCodeMetaRange(
|
||||
size_t rangeStart, size_t rangeEnd, size_t scopeStart, size_t scopeEnd)
|
||||
{
|
||||
if (rangeEnd <= rangeStart || scopeEnd <= scopeStart || rangeEnd <= scopeStart
|
||||
|| rangeStart >= scopeEnd) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
size_t start = std::max(rangeStart, scopeStart) - scopeStart;
|
||||
size_t end = std::min(rangeEnd, scopeEnd) - scopeStart;
|
||||
return CodeMetaRange{start, end, end - start};
|
||||
}
|
||||
|
||||
#endif // CODEMETA_RANGE_H
|
||||
@@ -9,14 +9,43 @@
|
||||
|
||||
#include "common/Configuration.h"
|
||||
|
||||
namespace {
|
||||
// Convert a QVariant holding a [r, g, b] list into an opaque rgba QJsonArray.
|
||||
// Returns an empty array if the input is not a 3+ element list of ints.
|
||||
static QJsonArray variantToRgba(const QVariant &v)
|
||||
{
|
||||
const auto arr = v.toList();
|
||||
if (arr.size() < 3 || !arr[0].canConvert<int>() || !arr[1].canConvert<int>()
|
||||
|| !arr[2].canConvert<int>()) {
|
||||
return {};
|
||||
}
|
||||
return {arr[0].toInt(), arr[1].toInt(), arr[2].toInt(), 255};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
const QStringList ColorThemeWorker::iaitoSpecificOptions
|
||||
= {"wordHighlight", "lineHighlight", "gui.main",
|
||||
"gui.imports", "highlightPC", "gui.navbar.err",
|
||||
"gui.navbar.seek", "gui.navbar.pc", "gui.navbar.sym",
|
||||
"gui.dataoffset", "gui.navbar.code", "gui.navbar.empty",
|
||||
"angui.navbar.str", "gui.disass_selected", "gui.breakpoint_background",
|
||||
"gui.overview.node", "gui.overview.fill", "gui.overview.border",
|
||||
"gui.border", "gui.background", "gui.alt_background",
|
||||
= {"wordHighlight",
|
||||
"lineHighlight",
|
||||
"gui.main",
|
||||
"gui.imports",
|
||||
"highlightPC",
|
||||
"gui.navbar.err",
|
||||
"gui.navbar.seek",
|
||||
"gui.navbar.pc",
|
||||
"gui.navbar.sym",
|
||||
"gui.dataoffset",
|
||||
"gui.navbar.code",
|
||||
"gui.navbar.data",
|
||||
"gui.navbar.empty",
|
||||
"gui.navbar.str",
|
||||
"gui.disass_selected",
|
||||
"gui.breakpoint_background",
|
||||
"gui.overview.node",
|
||||
"gui.overview.fill",
|
||||
"gui.overview.border",
|
||||
"gui.border",
|
||||
"gui.background",
|
||||
"gui.alt_background",
|
||||
"gui.disass_selected"};
|
||||
|
||||
const QStringList ColorThemeWorker::radare2UnusedOptions
|
||||
@@ -28,11 +57,7 @@ const QStringList ColorThemeWorker::radare2UnusedOptions
|
||||
ColorThemeWorker::ColorThemeWorker(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
#if R2_VERSION_NUMBER < 50709
|
||||
char *szThemes = r_str_home(R2_HOME_THEMES);
|
||||
#else
|
||||
char *szThemes = r_xdg_datadir("cons");
|
||||
#endif
|
||||
customR2ThemesLocationPath = szThemes;
|
||||
r_mem_free(szThemes);
|
||||
if (!QDir(customR2ThemesLocationPath).exists()) {
|
||||
@@ -100,6 +125,7 @@ QString ColorThemeWorker::save(const QJsonDocument &theme, const QString &themeN
|
||||
return tr("The file <b>%1</b> cannot be opened.").arg(QFileInfo(fOut).filePath());
|
||||
}
|
||||
|
||||
QByteArray buf;
|
||||
QJsonObject obj = theme.object();
|
||||
for (auto it = obj.constBegin(); it != obj.constEnd(); it++) {
|
||||
QJsonArray arr = it.value().toArray();
|
||||
@@ -114,16 +140,20 @@ QString ColorThemeWorker::save(const QJsonDocument &theme, const QString &themeN
|
||||
continue;
|
||||
}
|
||||
if (iaitoSpecificOptions.contains(it.key())) {
|
||||
fOut.write(QStringLiteral("#~%1 rgb:%2\n")
|
||||
.arg(it.key(), color.name(QColor::HexArgb).remove('#'))
|
||||
.toUtf8());
|
||||
buf += QStringLiteral("#~%1 rgb:%2\n")
|
||||
.arg(it.key(), color.name(QColor::HexArgb).remove('#'))
|
||||
.toUtf8();
|
||||
} else {
|
||||
fOut.write(QStringLiteral("ec %1 rgb:%2\n")
|
||||
.arg(it.key(), color.name(QColor::HexRgb).remove('#'))
|
||||
.toUtf8());
|
||||
buf += QStringLiteral("ec %1 rgb:%2\n")
|
||||
.arg(it.key(), color.name(QColor::HexRgb).remove('#'))
|
||||
.toUtf8();
|
||||
}
|
||||
}
|
||||
|
||||
if (fOut.write(buf) != buf.size() || !fOut.flush()) {
|
||||
fOut.close();
|
||||
return tr("Failed to write theme <b>%1</b>.").arg(themeName);
|
||||
}
|
||||
fOut.close();
|
||||
return "";
|
||||
}
|
||||
@@ -154,9 +184,10 @@ QJsonDocument ColorThemeWorker::getTheme(const QString &themeName) const
|
||||
}
|
||||
|
||||
for (auto it = theme.begin(); it != theme.end(); it++) {
|
||||
auto arr = it.value().toList();
|
||||
QColor(arr[0].toInt(), arr[1].toInt(), arr[2].toInt()).getRgb(&r, &g, &b, &a);
|
||||
theme[it.key()] = QJsonArray({r, g, b, a});
|
||||
const QJsonArray rgba = variantToRgba(it.value());
|
||||
if (!rgba.isEmpty()) {
|
||||
theme[it.key()] = rgba;
|
||||
}
|
||||
}
|
||||
|
||||
ColorFlags colorFlags = ColorFlags::DarkFlag;
|
||||
@@ -180,8 +211,10 @@ QJsonDocument ColorThemeWorker::getTheme(const QString &themeName) const
|
||||
if (sl.size() != 3 || sl[0][0] == '#') {
|
||||
continue;
|
||||
}
|
||||
QString optionName = sl[1] == "angui.navbar.str" ? QStringLiteral("gui.navbar.str")
|
||||
: sl[1];
|
||||
QColor(sl[2]).getRgb(&r, &g, &b, &a);
|
||||
theme.insert(sl[1], QJsonArray({r, g, b, a}));
|
||||
theme.insert(optionName, QJsonArray({r, g, b, a}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,12 +249,9 @@ QString ColorThemeWorker::deleteTheme(const QString &themeName) const
|
||||
}
|
||||
|
||||
QFile file(QDir(customR2ThemesLocationPath).filePath(themeName));
|
||||
if (file.isWritable()) {
|
||||
if (!QFileInfo(file).isWritable()) {
|
||||
return tr("You have no permission to write to <b>%1</b>").arg(QFileInfo(file).filePath());
|
||||
}
|
||||
if (!file.open(QFile::ReadOnly)) {
|
||||
return tr("File <b>%1</b> can not be opened.").arg(QFileInfo(file).filePath());
|
||||
}
|
||||
if (!file.remove()) {
|
||||
return tr("File <b>%1</b> can not be removed.").arg(QFileInfo(file).filePath());
|
||||
}
|
||||
@@ -262,6 +292,12 @@ QString ColorThemeWorker::importTheme(const QString &file) const
|
||||
|
||||
QString ColorThemeWorker::renameTheme(const QString &themeName, const QString &newName) const
|
||||
{
|
||||
// Reject path traversal / separators via radare2's strict name check.
|
||||
if (!r_name_check(themeName.toUtf8().constData())
|
||||
|| !r_name_check(newName.toUtf8().constData())) {
|
||||
return tr("Invalid theme name.");
|
||||
}
|
||||
|
||||
if (isThemeExist(newName)) {
|
||||
return tr("A color theme named <b>\"%1\"</b> already exists.").arg(newName);
|
||||
}
|
||||
@@ -290,7 +326,8 @@ bool ColorThemeWorker::isFileTheme(const QString &filePath, bool *ok) const
|
||||
}
|
||||
|
||||
const QString colors = "black|red|white|green|magenta|yellow|cyan|blue|gray|none";
|
||||
QString options = (Core()->cmdj("ecj").object().keys() << iaitoSpecificOptions)
|
||||
QString options = (Core()->cmdj("ecj").object().keys()
|
||||
<< iaitoSpecificOptions << QStringLiteral("angui.navbar.str"))
|
||||
.join('|')
|
||||
.replace(".", "\\.");
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "Colors.h"
|
||||
#include "common/Configuration.h"
|
||||
#include <vector>
|
||||
|
||||
Colors::Colors() {}
|
||||
|
||||
@@ -99,3 +100,21 @@ QString Colors::getColor(ut64 type)
|
||||
return "invalid";
|
||||
}
|
||||
}
|
||||
|
||||
QColor colorForIndex(unsigned int i)
|
||||
{
|
||||
static const std::vector<QColor> palette = {
|
||||
QColor("#1ABC9C"), // TURQUOISE
|
||||
QColor("#2ECC71"), // EMERALD
|
||||
QColor("#3498DB"), // PETER RIVER
|
||||
QColor("#9B59B6"), // AMETHYST
|
||||
QColor("#34495E"), // WET ASPHALT
|
||||
QColor("#F1C40F"), // SUN FLOWER
|
||||
QColor("#E67E22"), // CARROT
|
||||
QColor("#E74C3C"), // ALIZARIN
|
||||
QColor("#ECF0F1"), // CLOUDS
|
||||
QColor("#BDC3C7"), // SILVER
|
||||
QColor("#95A5A6") // CONCRETE
|
||||
};
|
||||
return palette[i % palette.size()];
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
#ifndef COLORS_H
|
||||
#define COLORS_H
|
||||
|
||||
#include "common/Radare2Compat.h"
|
||||
#include "common/RichTextPainter.h"
|
||||
#include "core/Iaito.h"
|
||||
#include <r_anal.h>
|
||||
#include <QColor>
|
||||
|
||||
class Colors
|
||||
{
|
||||
@@ -13,4 +14,7 @@ public:
|
||||
static QString getColor(ut64 type);
|
||||
};
|
||||
|
||||
// Return a consistent QColor for the given index (round-robin over a palette)
|
||||
QColor colorForIndex(unsigned int i);
|
||||
|
||||
#endif // COLORS_H
|
||||
@@ -2,17 +2,19 @@
|
||||
#include "CommandTask.h"
|
||||
#include "TempConfig.h"
|
||||
|
||||
CommandTask::CommandTask(const QString &cmd, ColorMode colorMode, bool outFormatHtml)
|
||||
CommandTask::CommandTask(
|
||||
const QString &cmd, ColorMode colorMode, bool outFormatHtml, ExecutionMode executionMode)
|
||||
: cmd(cmd)
|
||||
, colorMode(colorMode)
|
||||
, outFormatHtml(outFormatHtml)
|
||||
, executionMode(executionMode)
|
||||
{}
|
||||
|
||||
void CommandTask::runTask()
|
||||
{
|
||||
TempConfig tempConfig;
|
||||
tempConfig.set("scr.color", colorMode);
|
||||
auto res = Core()->cmdTask(cmd);
|
||||
auto res = executionMode == ExecutionMode::DIRECT_CMD ? Core()->cmd(cmd) : Core()->cmdTask(cmd);
|
||||
if (outFormatHtml) {
|
||||
res = IaitoCore::ansiEscapeToHtml(res);
|
||||
}
|
||||
|
||||
@@ -17,8 +17,13 @@ public:
|
||||
MODE_16M = COLOR_MODE_16M
|
||||
};
|
||||
|
||||
enum ExecutionMode { CORE_TASK, DIRECT_CMD };
|
||||
|
||||
CommandTask(
|
||||
const QString &cmd, ColorMode colorMode = ColorMode::DISABLED, bool outFormatHtml = false);
|
||||
const QString &cmd,
|
||||
ColorMode colorMode = ColorMode::DISABLED,
|
||||
bool outFormatHtml = false,
|
||||
ExecutionMode executionMode = ExecutionMode::CORE_TASK);
|
||||
|
||||
QString getTitle() override { return tr("Running Command"); }
|
||||
|
||||
@@ -32,6 +37,7 @@ private:
|
||||
QString cmd;
|
||||
ColorMode colorMode;
|
||||
bool outFormatHtml;
|
||||
ExecutionMode executionMode;
|
||||
};
|
||||
|
||||
#endif // COMMANDTASK_H
|
||||
@@ -0,0 +1,10 @@
|
||||
#ifndef IAITO_COMPILER_WORKAROUNDS_H
|
||||
#define IAITO_COMPILER_WORKAROUNDS_H
|
||||
|
||||
#if defined(__APPLE__) && defined(__aarch64__) && defined(__clang__)
|
||||
#if __has_include(<arm_acle.h>)
|
||||
#include <arm_acle.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#endif // IAITO_COMPILER_WORKAROUNDS_H
|
||||
@@ -1,10 +1,23 @@
|
||||
#include "Configuration.h"
|
||||
#include <QAbstractItemView>
|
||||
#include <QApplication>
|
||||
#include <QDir>
|
||||
#include <QEvent>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QFontDatabase>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QLineEdit>
|
||||
#include <QPlainTextEdit>
|
||||
#include <QPushButton>
|
||||
#include <QSet>
|
||||
#include <QStandardPaths>
|
||||
#include <QStyle>
|
||||
#include <QTextEdit>
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
|
||||
#include <QStyleHints>
|
||||
#endif
|
||||
|
||||
#ifdef IAITO_ENABLE_KSYNTAXHIGHLIGHTING
|
||||
#include <KSyntaxHighlighting/Definition>
|
||||
@@ -15,6 +28,8 @@
|
||||
#include "common/ColorThemeWorker.h"
|
||||
#include "common/ResourcePaths.h"
|
||||
#include "common/SyntaxHighlighter.h"
|
||||
#include "common/theme/IaitoStyle.h"
|
||||
#include "common/theme/Theme.h"
|
||||
|
||||
#include <QMetaType>
|
||||
#include <QVariant>
|
||||
@@ -28,6 +43,15 @@ static inline int variantTypeId(const QVariant &v)
|
||||
return v.userType();
|
||||
#endif
|
||||
}
|
||||
|
||||
constexpr int VisualNavbarThicknessDefault = 15;
|
||||
constexpr int VisualNavbarThicknessMin = 8;
|
||||
constexpr int VisualNavbarThicknessMax = 128;
|
||||
constexpr int VisualNavbarResolutionMax = 32;
|
||||
constexpr const char *AddressRangeSelectionSyncKey = "syncAddressRangeSelection";
|
||||
constexpr const char *DisassemblyJumpLineAdaptiveSpacingKey = "disassemblyJumpLineAdaptiveSpacing";
|
||||
constexpr const char *DisassemblyJumpLineSpacingKey = "disassemblyJumpLineSpacing";
|
||||
constexpr int DisassemblyJumpLineSpacingDefault = 20;
|
||||
} // namespace
|
||||
|
||||
/* Map with names of themes associated with its color palette
|
||||
@@ -50,7 +74,7 @@ const QHash<QString, ColorFlags> Configuration::relevantThemes
|
||||
{"ogray", DarkFlag},
|
||||
{"onedark", DarkFlag},
|
||||
{"pink", DarkFlag},
|
||||
{"rasta", DarkFlag},
|
||||
{"rasta", static_cast<ColorFlags>(DarkFlag | LightFlag)},
|
||||
{"sepia", DarkFlag},
|
||||
{"smyck", DarkFlag},
|
||||
{"solarized", DarkFlag},
|
||||
@@ -60,7 +84,6 @@ const QHash<QString, ColorFlags> Configuration::relevantThemes
|
||||
{"iaito", LightFlag},
|
||||
{"dark", LightFlag},
|
||||
{"matrix", LightFlag},
|
||||
{"rasta", LightFlag},
|
||||
{"tango", LightFlag},
|
||||
{"white", LightFlag},
|
||||
{"white2", LightFlag}};
|
||||
@@ -86,6 +109,8 @@ const QHash<QString, QHash<ColorFlags, QColor>> Configuration::iaitoOptionColors
|
||||
{{DarkFlag, QColor(0x42, 0xee, 0xf4)}, {LightFlag, QColor(0x42, 0xee, 0xf4)}}},
|
||||
{"gui.navbar.code",
|
||||
{{DarkFlag, QColor(0x82, 0xa8, 0x6f)}, {LightFlag, QColor(0x68, 0xc5, 0x45)}}},
|
||||
{"gui.navbar.data",
|
||||
{{DarkFlag, QColor(0xa0, 0x94, 0x68)}, {LightFlag, QColor(0xb0, 0x87, 0x2f)}}},
|
||||
{"gui.navbar.str",
|
||||
{{DarkFlag, QColor(0x6f, 0x86, 0xd8)}, {LightFlag, QColor(0x45, 0x68, 0xe5)}}},
|
||||
{"gui.navbar.sym",
|
||||
@@ -108,11 +133,11 @@ const QHash<QString, QHash<ColorFlags, QColor>> Configuration::iaitoOptionColors
|
||||
{"gui.disass_selected",
|
||||
{{DarkFlag, QColor(0x1f, 0x22, 0x28)}, {LightFlag, QColor(0xff, 0xff, 0xff)}}},
|
||||
{"lineHighlight",
|
||||
{{DarkFlag, QColor(0x15, 0x1d, 0x1d, 0x96)}, {LightFlag, QColor(0xd2, 0xd2, 0xff, 0x96)}}},
|
||||
{{DarkFlag, QColor(0x4d, 0x58, 0x6b, 0xb4)}, {LightFlag, QColor(0xd2, 0xd2, 0xff, 0x96)}}},
|
||||
{"wordHighlight",
|
||||
{{DarkFlag, QColor(0x34, 0x3a, 0x47, 0xff)}, {LightFlag, QColor(0xb3, 0x77, 0xd6, 0x3c)}}},
|
||||
{"highlightPC",
|
||||
{{DarkFlag, QColor(0x57, 0x1a, 0x07)}, {LightFlag, QColor(0xd6, 0xff, 0xd2)}}},
|
||||
{{DarkFlag, QColor(0x14, 0x30, 0x3d)}, {LightFlag, QColor(0xd2, 0xec, 0xf5)}}},
|
||||
{"gui.overview.fill",
|
||||
{{DarkFlag, QColor(0xff, 0xff, 0xff, 0x28)}, {LightFlag, QColor(0xaf, 0xd9, 0xea, 0x41)}}},
|
||||
{"gui.overview.border",
|
||||
@@ -138,7 +163,7 @@ static const QHash<QString, QVariant> asmOptions
|
||||
{"asm.emu", false},
|
||||
{"emu.str", true},
|
||||
{"asm.cmt.right", true},
|
||||
{"asm.cmt.col", 60},
|
||||
{"asm.cmt.col", 30},
|
||||
{"asm.var.summary", 4},
|
||||
{"asm.bytes", false},
|
||||
{"asm.size", false},
|
||||
@@ -157,22 +182,16 @@ static const QHash<QString, QVariant> asmOptions
|
||||
{"asm.refptr", false},
|
||||
{"asm.flags.real", true},
|
||||
{"esil.breakoninvalid", false},
|
||||
#if R2_VERSION_NUMBER >= 50909
|
||||
{"graph.addr", true},
|
||||
{"graph.addr", false},
|
||||
{"asm.addr", true},
|
||||
{"asm.addr.relto", ""},
|
||||
{"asm.flags.addr", false}
|
||||
#else
|
||||
{"graph.offset", true},
|
||||
{"asm.offset", true},
|
||||
{"asm.offset.relative", false},
|
||||
{"asm.offset.flags", false}
|
||||
#endif
|
||||
};
|
||||
{"asm.addr.base", "16"},
|
||||
{"asm.flags.offset", false}};
|
||||
|
||||
Configuration::Configuration()
|
||||
: QObject()
|
||||
, nativePalette(qApp->palette())
|
||||
, defaultAppFont(qApp->font())
|
||||
{
|
||||
mPtr = this;
|
||||
if (!s.isWritable()) {
|
||||
@@ -186,6 +205,60 @@ Configuration::Configuration()
|
||||
#ifdef IAITO_ENABLE_KSYNTAXHIGHLIGHTING
|
||||
kSyntaxHighlightingRepository = nullptr;
|
||||
#endif
|
||||
|
||||
// Live system theme detection: when the OS color scheme flips, re-apply
|
||||
// the Native interface theme without requiring a restart.
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
|
||||
if (auto hints = QGuiApplication::styleHints()) {
|
||||
connect(hints, &QStyleHints::colorSchemeChanged, this, [this](Qt::ColorScheme) {
|
||||
onSystemColorSchemeChanged();
|
||||
});
|
||||
}
|
||||
#endif
|
||||
// Fallback for older Qt and for platforms that deliver the change via
|
||||
// a palette event rather than QStyleHints.
|
||||
if (qApp) {
|
||||
qApp->installEventFilter(this);
|
||||
}
|
||||
}
|
||||
|
||||
bool Configuration::eventFilter(QObject *watched, QEvent *event)
|
||||
{
|
||||
if (watched == qApp && event->type() == QEvent::ApplicationPaletteChange) {
|
||||
onSystemColorSchemeChanged();
|
||||
}
|
||||
return QObject::eventFilter(watched, event);
|
||||
}
|
||||
|
||||
void Configuration::onSystemColorSchemeChanged()
|
||||
{
|
||||
if (applyingTheme) {
|
||||
return;
|
||||
}
|
||||
// Refresh our captured native palette so subsequent Native theme
|
||||
// activations reflect the new OS palette. qApp->palette() is the best
|
||||
// portable handle we have; if Qt auto-updated it we pick up the new
|
||||
// system colors, otherwise we fall back to the style's standard palette.
|
||||
QPalette fresh = qApp->palette();
|
||||
if (auto style = qApp->style()) {
|
||||
QPalette stylePalette = style->standardPalette();
|
||||
// Merge: prefer the style's window color, which is what drives
|
||||
// nativeWindowIsDark() on platforms where Qt does not push the new
|
||||
// system palette into qApp->palette() automatically.
|
||||
fresh.setColor(QPalette::Window, stylePalette.color(QPalette::Window));
|
||||
fresh.setColor(QPalette::WindowText, stylePalette.color(QPalette::WindowText));
|
||||
fresh.setColor(QPalette::Base, stylePalette.color(QPalette::Base));
|
||||
fresh.setColor(QPalette::Text, stylePalette.color(QPalette::Text));
|
||||
}
|
||||
nativePalette = fresh;
|
||||
|
||||
// Only re-apply if the user picked the Native theme; otherwise the
|
||||
// selected theme is independent of the OS setting.
|
||||
const IaitoInterfaceTheme *current = getCurrentTheme();
|
||||
if (!current || current->name != QStringLiteral("Native")) {
|
||||
return;
|
||||
}
|
||||
setInterfaceTheme(getInterfaceTheme());
|
||||
}
|
||||
|
||||
Configuration *Configuration::instance()
|
||||
@@ -234,6 +307,16 @@ void Configuration::setRecentFolder(const QString &dir)
|
||||
s.setValue("dir.recentFolder", QDir::toNativeSeparators(dir));
|
||||
}
|
||||
|
||||
QString Configuration::getSha256LookupBaseUrl() const
|
||||
{
|
||||
return s.value("sha256.lookupBaseUrl", "https://www.virustotal.com/gui/file/").toString();
|
||||
}
|
||||
|
||||
void Configuration::setSha256LookupBaseUrl(const QString &url)
|
||||
{
|
||||
s.setValue("sha256.lookupBaseUrl", url);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Configuration::setFilesTabLastClicked
|
||||
* Set the new file dialog last clicked tab
|
||||
@@ -266,16 +349,6 @@ void Configuration::resetAll()
|
||||
emit fontsUpdated();
|
||||
}
|
||||
|
||||
bool Configuration::getAutoUpdateEnabled() const
|
||||
{
|
||||
return s.value("autoUpdateEnabled", false).toBool();
|
||||
}
|
||||
|
||||
void Configuration::setAutoUpdateEnabled(bool au)
|
||||
{
|
||||
s.setValue("autoUpdateEnabled", au);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief get the current Locale set in Iaito's user configuration
|
||||
* @return a QLocale object describes user's current locale
|
||||
@@ -326,113 +399,208 @@ bool Configuration::windowColorIsDark()
|
||||
|
||||
bool Configuration::nativeWindowIsDark()
|
||||
{
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
|
||||
if (auto hints = QGuiApplication::styleHints()) {
|
||||
Qt::ColorScheme scheme = hints->colorScheme();
|
||||
if (scheme != Qt::ColorScheme::Unknown) {
|
||||
return scheme == Qt::ColorScheme::Dark;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
const QPalette &palette = qApp->palette();
|
||||
auto windowColor = palette.color(QPalette::Window).toRgb();
|
||||
return (windowColor.red() + windowColor.green() + windowColor.blue()) < 382;
|
||||
}
|
||||
|
||||
void Configuration::loadNativeStylesheet()
|
||||
static void applyFontEverywhere(const QFont &font)
|
||||
{
|
||||
/* Load Qt Theme */
|
||||
QFile f(":native/native.qss");
|
||||
if (!f.exists()) {
|
||||
qWarning() << "Can't find Native theme stylesheet.";
|
||||
} else {
|
||||
f.open(QFile::ReadOnly | QFile::Text);
|
||||
QTextStream ts(&f);
|
||||
QString stylesheet = ts.readAll();
|
||||
qApp->setStyleSheet(stylesheet);
|
||||
qApp->setFont(font);
|
||||
for (const char *cls : {"QWidget", "QMenu", "QMenuBar",
|
||||
"QPushButton", "QToolButton", "QHeaderView",
|
||||
"QAbstractItemView", "QListView", "QTreeView",
|
||||
"QTableView", "QComboBox", "QLineEdit",
|
||||
"QTextEdit", "QPlainTextEdit", "QLabel",
|
||||
"QGroupBox", "QTabBar", "QTabWidget",
|
||||
"QStatusBar", "QToolTip", "QCheckBox",
|
||||
"QRadioButton", "QSpinBox", "QDoubleSpinBox",
|
||||
"QAbstractSpinBox", "QDockWidget", "QToolBar",
|
||||
"QDialog", "QMainWindow"}) {
|
||||
qApp->setFont(font, cls);
|
||||
}
|
||||
|
||||
qApp->setPalette(nativePalette);
|
||||
/* Some widgets does not change its palette when QApplication changes one
|
||||
* so this loop force all widgets do this, but all widgets take palette from
|
||||
* QApplication::palette() when they are created so line above is necessary
|
||||
* too.
|
||||
*/
|
||||
for (auto widget : qApp->allWidgets()) {
|
||||
widget->setPalette(nativePalette);
|
||||
widget->setFont(font);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Loads the Light theme of Iaito and modify special theme colors
|
||||
*/
|
||||
void Configuration::loadLightStylesheet()
|
||||
Theme Configuration::nativeEngineTheme()
|
||||
{
|
||||
/* Load Qt Theme */
|
||||
QFile f(":lightstyle/light.qss");
|
||||
if (!f.exists()) {
|
||||
qWarning() << "Can't find Light theme stylesheet.";
|
||||
} else {
|
||||
f.open(QFile::ReadOnly | QFile::Text);
|
||||
QTextStream ts(&f);
|
||||
QString stylesheet = ts.readAll();
|
||||
return Theme::fromPalette(nativePalette, nativeWindowIsDark());
|
||||
}
|
||||
|
||||
QPalette p = qApp->palette();
|
||||
p.setColor(QPalette::Text, Qt::black);
|
||||
qApp->setPalette(p);
|
||||
|
||||
qApp->setStyleSheet(stylesheet);
|
||||
void Configuration::loadEngineTheme(const Theme &t)
|
||||
{
|
||||
qApp->setStyleSheet(QString());
|
||||
m_interfaceChrome = t.toChrome();
|
||||
const QPalette p = t.toPalette(nativePalette);
|
||||
qApp->setPalette(p);
|
||||
applyFontEverywhere(t.fonts.custom ? t.fonts.base : defaultAppFont);
|
||||
for (QWidget *w : qApp->allWidgets()) {
|
||||
w->setPalette(p);
|
||||
}
|
||||
if (IaitoStyle::instance()) {
|
||||
IaitoStyle::instance()->setTheme(t);
|
||||
}
|
||||
}
|
||||
|
||||
void Configuration::loadDarkStylesheet()
|
||||
QString Configuration::userThemesDir()
|
||||
{
|
||||
/* Load Qt Theme */
|
||||
QFile f(":qdarkstyle/style.qss");
|
||||
if (!f.exists()) {
|
||||
qWarning() << "Can't find Dark theme stylesheet.";
|
||||
} else {
|
||||
f.open(QFile::ReadOnly | QFile::Text);
|
||||
QTextStream ts(&f);
|
||||
QString stylesheet = ts.readAll();
|
||||
#ifdef Q_OS_MACOS
|
||||
// see
|
||||
// https://github.com/ColinDuquesnoy/QDarkStyleSheet/issues/22#issuecomment-96179529
|
||||
stylesheet += "QDockWidget::title"
|
||||
"{"
|
||||
" background-color: #31363b;"
|
||||
" text-align: center;"
|
||||
" height: 12px;"
|
||||
"}";
|
||||
#endif
|
||||
QPalette p = qApp->palette();
|
||||
p.setColor(QPalette::Text, Qt::white);
|
||||
qApp->setPalette(p);
|
||||
qApp->setStyleSheet(stylesheet);
|
||||
}
|
||||
const QString dir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)
|
||||
+ QDir::separator() + QStringLiteral("themes");
|
||||
QDir().mkpath(dir);
|
||||
return dir;
|
||||
}
|
||||
|
||||
void Configuration::loadMidnightStylesheet()
|
||||
bool Configuration::isCustomInterfaceTheme(const QString &name)
|
||||
{
|
||||
/* Load Qt Theme */
|
||||
QFile f(":midnight/style.css");
|
||||
if (!f.exists()) {
|
||||
qWarning() << "Can't find Midnight theme stylesheet.";
|
||||
} else {
|
||||
f.open(QFile::ReadOnly | QFile::Text);
|
||||
QTextStream ts(&f);
|
||||
QString stylesheet = ts.readAll();
|
||||
return QFileInfo::exists(QDir(userThemesDir()).filePath(name + QStringLiteral(".theme")));
|
||||
}
|
||||
|
||||
QPalette p = qApp->palette();
|
||||
p.setColor(QPalette::Text, Qt::white);
|
||||
qApp->setPalette(p);
|
||||
bool Configuration::isValidThemeName(const QString &name)
|
||||
{
|
||||
return r_name_check(name.toUtf8().constData());
|
||||
}
|
||||
|
||||
qApp->setStyleSheet(stylesheet);
|
||||
const QStringList &Configuration::interfaceThemeVariableKeys()
|
||||
{
|
||||
static const QStringList keys
|
||||
= {"palette/background",
|
||||
"palette/panel",
|
||||
"palette/surface",
|
||||
"palette/border",
|
||||
"palette/text",
|
||||
"palette/mutedText",
|
||||
"palette/accent",
|
||||
"palette/accentText"};
|
||||
return keys;
|
||||
}
|
||||
|
||||
QHash<QString, QColor> Configuration::defaultInterfaceThemeVariables()
|
||||
{
|
||||
QWidget base;
|
||||
QLineEdit edit;
|
||||
QPushButton button;
|
||||
base.ensurePolished();
|
||||
edit.ensurePolished();
|
||||
button.ensurePolished();
|
||||
const QPalette &bp = base.palette();
|
||||
return {
|
||||
{"palette/background", bp.color(QPalette::Window)},
|
||||
{"palette/panel", edit.palette().color(QPalette::Base)},
|
||||
{"palette/surface", button.palette().color(QPalette::Button)},
|
||||
{"palette/border", bp.color(QPalette::Midlight)},
|
||||
{"palette/text", bp.color(QPalette::WindowText)},
|
||||
{"palette/mutedText", bp.color(QPalette::Disabled, QPalette::WindowText)},
|
||||
{"palette/accent", bp.color(QPalette::Highlight)},
|
||||
{"palette/accentText", bp.color(QPalette::HighlightedText)}};
|
||||
}
|
||||
|
||||
QHash<QString, QColor> Configuration::loadInterfaceThemeVariables(const QString &name)
|
||||
{
|
||||
QHash<QString, QColor> vars = defaultInterfaceThemeVariables();
|
||||
QSettings
|
||||
theme(QDir(userThemesDir()).filePath(name + QStringLiteral(".theme")), QSettings::IniFormat);
|
||||
for (const QString &key : interfaceThemeVariableKeys()) {
|
||||
const QColor color(theme.value(key).toString());
|
||||
if (color.isValid()) {
|
||||
vars[key] = color;
|
||||
}
|
||||
}
|
||||
return vars;
|
||||
}
|
||||
|
||||
void Configuration::saveInterfaceThemeVariables(
|
||||
const QString &name, const QHash<QString, QColor> &vars, bool dark)
|
||||
{
|
||||
QSettings
|
||||
theme(QDir(userThemesDir()).filePath(name + QStringLiteral(".theme")), QSettings::IniFormat);
|
||||
for (const QString &key : interfaceThemeVariableKeys()) {
|
||||
const QColor c = vars.value(key);
|
||||
theme.setValue(key, c.alpha() == 255 ? c.name(QColor::HexRgb) : c.name(QColor::HexArgb));
|
||||
}
|
||||
theme.setValue(QStringLiteral("meta/dark"), dark);
|
||||
theme.setValue(QStringLiteral("meta/name"), name);
|
||||
}
|
||||
|
||||
void Configuration::applyInterfaceVariables(const QHash<QString, QColor> &vars, bool dark)
|
||||
{
|
||||
loadEngineTheme(Theme::fromChrome(vars, dark));
|
||||
}
|
||||
|
||||
bool Configuration::interfaceThemeIsDark(const QString &name)
|
||||
{
|
||||
if (isCustomInterfaceTheme(name)) {
|
||||
QSettings
|
||||
s(QDir(userThemesDir()).filePath(name + QStringLiteral(".theme")), QSettings::IniFormat);
|
||||
if (s.contains(QStringLiteral("meta/dark"))) {
|
||||
return s.value(QStringLiteral("meta/dark")).toBool();
|
||||
}
|
||||
}
|
||||
const auto &list = iaitoInterfaceThemesList();
|
||||
for (const IaitoInterfaceTheme &t : list) {
|
||||
if (t.name == name) {
|
||||
return t.flag == DarkFlag;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Configuration::loadUserStylesheet(const QString &name)
|
||||
{
|
||||
if (!isCustomInterfaceTheme(name)) {
|
||||
return false;
|
||||
}
|
||||
const QString path = QDir(userThemesDir()).filePath(name + QStringLiteral(".theme"));
|
||||
Theme t = Theme::fromChrome(loadInterfaceThemeVariables(name), interfaceThemeIsDark(name));
|
||||
t.applyIni(path);
|
||||
if (t.name.isEmpty()) {
|
||||
t.name = name;
|
||||
}
|
||||
loadEngineTheme(t);
|
||||
return true;
|
||||
}
|
||||
|
||||
const QFont Configuration::getBaseFont() const
|
||||
{
|
||||
QFont font = s.value("font", QFont("Inconsolata", 11)).value<QFont>();
|
||||
return font;
|
||||
if (IaitoStyle::instance() && IaitoStyle::instance()->theme().fonts.custom) {
|
||||
return IaitoStyle::instance()->theme().fonts.base;
|
||||
}
|
||||
return s.value("font", QFont("IBM Plex Mono", 13)).value<QFont>();
|
||||
}
|
||||
|
||||
const QFont Configuration::getFont() const
|
||||
{
|
||||
QFont font = getBaseFont();
|
||||
font.setPointSizeF(font.pointSizeF() * getZoomFactor());
|
||||
qreal zoom = getZoomFactor();
|
||||
if (font.pixelSize() > 0) {
|
||||
font.setPixelSize(qMax(int(font.pixelSize() * zoom), 1));
|
||||
} else {
|
||||
font.setPointSizeF(font.pointSizeF() * zoom);
|
||||
}
|
||||
return font;
|
||||
}
|
||||
|
||||
const QFont Configuration::getSmallFont(bool zoomed) const
|
||||
{
|
||||
if (IaitoStyle::instance() && IaitoStyle::instance()->theme().fonts.custom) {
|
||||
return zoomed ? getFont() : getBaseFont();
|
||||
}
|
||||
QFont font = getBaseFont();
|
||||
qreal eff = (zoomed ? getZoomFactor() : 1.0) * 0.85;
|
||||
if (font.pixelSize() > 0) {
|
||||
font.setPixelSize(qMax(int(font.pixelSize() * eff), 8));
|
||||
} else {
|
||||
font.setPointSizeF(qMax(font.pointSizeF() * eff, 6.0));
|
||||
}
|
||||
return font;
|
||||
}
|
||||
|
||||
@@ -459,6 +627,87 @@ void Configuration::setZoomFactor(qreal zoom)
|
||||
emit fontsUpdated();
|
||||
}
|
||||
|
||||
Configuration::VisualNavbarLocation Configuration::getVisualNavbarLocation() const
|
||||
{
|
||||
auto location = static_cast<VisualNavbarLocation>(
|
||||
s.value("visualNavbarLocation", static_cast<int>(VisualNavbarLocation::Left)).toInt());
|
||||
switch (location) {
|
||||
case VisualNavbarLocation::Top:
|
||||
case VisualNavbarLocation::Bottom:
|
||||
case VisualNavbarLocation::Left:
|
||||
case VisualNavbarLocation::Right:
|
||||
case VisualNavbarLocation::SuperTop:
|
||||
case VisualNavbarLocation::SuperBottom:
|
||||
return location;
|
||||
}
|
||||
return VisualNavbarLocation::Left;
|
||||
}
|
||||
|
||||
void Configuration::setVisualNavbarLocation(VisualNavbarLocation location)
|
||||
{
|
||||
if (getVisualNavbarLocation() == location) {
|
||||
return;
|
||||
}
|
||||
s.setValue("visualNavbarLocation", static_cast<int>(location));
|
||||
emit visualNavbarLocationChanged(location);
|
||||
}
|
||||
|
||||
int Configuration::getVisualNavbarThickness() const
|
||||
{
|
||||
int thickness = s.value("visualNavbarThickness", VisualNavbarThicknessDefault).toInt();
|
||||
return qBound(VisualNavbarThicknessMin, thickness, VisualNavbarThicknessMax);
|
||||
}
|
||||
|
||||
void Configuration::setVisualNavbarThickness(int thickness)
|
||||
{
|
||||
thickness = qBound(VisualNavbarThicknessMin, thickness, VisualNavbarThicknessMax);
|
||||
if (getVisualNavbarThickness() == thickness) {
|
||||
return;
|
||||
}
|
||||
|
||||
s.setValue("visualNavbarThickness", thickness);
|
||||
emit visualNavbarThicknessChanged(thickness);
|
||||
}
|
||||
|
||||
int Configuration::getVisualNavbarResolution() const
|
||||
{
|
||||
return qBound(1, s.value("visualNavbarResolution", 1).toInt(), VisualNavbarResolutionMax);
|
||||
}
|
||||
|
||||
void Configuration::setVisualNavbarResolution(int divisor)
|
||||
{
|
||||
divisor = qBound(1, divisor, VisualNavbarResolutionMax);
|
||||
if (getVisualNavbarResolution() == divisor) {
|
||||
return;
|
||||
}
|
||||
s.setValue("visualNavbarResolution", divisor);
|
||||
emit visualNavbarResolutionChanged(divisor);
|
||||
}
|
||||
|
||||
Configuration::MemoryScrollBarMode Configuration::getMemoryScrollBarMode() const
|
||||
{
|
||||
const int mode
|
||||
= s.value("memoryScrollBarMode", static_cast<int>(MemoryScrollBarMode::Bounded)).toInt();
|
||||
switch (mode) {
|
||||
case static_cast<int>(MemoryScrollBarMode::Spring):
|
||||
return MemoryScrollBarMode::Spring;
|
||||
case static_cast<int>(MemoryScrollBarMode::Hidden):
|
||||
return MemoryScrollBarMode::Hidden;
|
||||
case static_cast<int>(MemoryScrollBarMode::Bounded):
|
||||
default:
|
||||
return MemoryScrollBarMode::Bounded;
|
||||
}
|
||||
}
|
||||
|
||||
void Configuration::setMemoryScrollBarMode(MemoryScrollBarMode mode)
|
||||
{
|
||||
if (getMemoryScrollBarMode() == mode) {
|
||||
return;
|
||||
}
|
||||
s.setValue("memoryScrollBarMode", static_cast<int>(mode));
|
||||
emit memoryScrollBarModeChanged(mode);
|
||||
}
|
||||
|
||||
QString Configuration::getLastThemeOf(const IaitoInterfaceTheme &currInterfaceTheme) const
|
||||
{
|
||||
return s.value("lastThemeOf." + currInterfaceTheme.name, Config()->getColorTheme()).toString();
|
||||
@@ -473,26 +722,51 @@ void Configuration::setInterfaceTheme(int theme)
|
||||
|
||||
IaitoInterfaceTheme interfaceTheme = iaitoInterfaceThemesList()[theme];
|
||||
|
||||
applyingTheme = true;
|
||||
static const QSet<QString> builtinEngineThemes
|
||||
= {"Dark",
|
||||
"Midnight",
|
||||
"Light",
|
||||
"Classic",
|
||||
"Pro",
|
||||
"Pro Dark",
|
||||
"Nord",
|
||||
"High Contrast",
|
||||
"Cobalt",
|
||||
"Capsule",
|
||||
"Phosphor"};
|
||||
if (interfaceTheme.name == "Native") {
|
||||
loadNativeStylesheet();
|
||||
} else if (interfaceTheme.name == "Dark") {
|
||||
loadDarkStylesheet();
|
||||
} else if (interfaceTheme.name == "Midnight") {
|
||||
loadMidnightStylesheet();
|
||||
} else if (interfaceTheme.name == "Light") {
|
||||
loadLightStylesheet();
|
||||
} else {
|
||||
loadNativeStylesheet();
|
||||
loadEngineTheme(nativeEngineTheme());
|
||||
} else if (builtinEngineThemes.contains(interfaceTheme.name)) {
|
||||
loadEngineTheme(Theme::builtin(interfaceTheme.name));
|
||||
} else if (!loadUserStylesheet(interfaceTheme.name)) {
|
||||
loadEngineTheme(nativeEngineTheme());
|
||||
}
|
||||
|
||||
for (auto it = iaitoOptionColors.cbegin(); it != iaitoOptionColors.cend(); it++) {
|
||||
setColor(it.key(), it.value()[interfaceTheme.flag]);
|
||||
}
|
||||
|
||||
// adjustColorThemeDarkness() may call setColorTheme() which emits a
|
||||
// heavy colorsUpdated() by itself; in that case we don't need to emit
|
||||
// another one. Otherwise this is a palette-only change and heavy
|
||||
// widgets can skip their content reloads.
|
||||
QString prevColorTheme = getColorTheme();
|
||||
adjustColorThemeDarkness();
|
||||
bool colorThemeChanged = (prevColorTheme != getColorTheme());
|
||||
applyingTheme = false;
|
||||
|
||||
emit interfaceThemeChanged();
|
||||
emit colorsUpdated();
|
||||
if (!colorThemeChanged) {
|
||||
paletteOnlyUpdate = true;
|
||||
emit colorsUpdated();
|
||||
emit interfacePaletteUpdated();
|
||||
paletteOnlyUpdate = false;
|
||||
} else {
|
||||
// adjustColorThemeDarkness -> setColorTheme already emitted the
|
||||
// heavy colorsUpdated(). Still notify palette-only listeners.
|
||||
emit interfacePaletteUpdated();
|
||||
}
|
||||
#ifdef IAITO_ENABLE_KSYNTAXHIGHLIGHTING
|
||||
emit kSyntaxHighlightingThemeChanged();
|
||||
#endif
|
||||
@@ -565,6 +839,9 @@ void Configuration::setLastThemeOf(
|
||||
|
||||
const QColor Configuration::getColor(const QString &name) const
|
||||
{
|
||||
if (name.contains(QLatin1Char('/'))) {
|
||||
return m_interfaceChrome.value(name);
|
||||
}
|
||||
if (s.contains("colors." + name)) {
|
||||
return s.value("colors." + name).value<QColor>();
|
||||
} else {
|
||||
@@ -591,6 +868,13 @@ void Configuration::setColorTheme(const QString &theme)
|
||||
setColor(it.key(), QColor(rgb[0].toInt(), rgb[1].toInt(), rgb[2].toInt(), rgb[3].toInt()));
|
||||
}
|
||||
|
||||
// Derive highlightPC from graph.current with reduced alpha for use as background
|
||||
QColor graphCurrent = getColor("graph.current");
|
||||
if (graphCurrent.isValid()) {
|
||||
graphCurrent.setAlpha(0x30);
|
||||
setColor("highlightPC", graphCurrent);
|
||||
}
|
||||
|
||||
emit colorsUpdated();
|
||||
}
|
||||
|
||||
@@ -614,11 +898,53 @@ int Configuration::colorThemeDarkness(const QString &colorTheme) const
|
||||
return DarkFlag | LightFlag;
|
||||
}
|
||||
|
||||
bool Configuration::getAddressRangeSelectionSyncEnabled() const
|
||||
{
|
||||
return s.value(AddressRangeSelectionSyncKey, true).toBool();
|
||||
}
|
||||
|
||||
void Configuration::setAddressRangeSelectionSyncEnabled(bool enabled)
|
||||
{
|
||||
if (getAddressRangeSelectionSyncEnabled() == enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
s.setValue(AddressRangeSelectionSyncKey, enabled);
|
||||
if (!enabled) {
|
||||
Core()->clearAddressRangeSelection();
|
||||
}
|
||||
emit addressRangeSelectionSyncEnabledChanged(enabled);
|
||||
}
|
||||
|
||||
void Configuration::resetToDefaultAsmOptions()
|
||||
{
|
||||
for (auto it = asmOptions.cbegin(); it != asmOptions.cend(); it++) {
|
||||
setConfig(it.key(), it.value());
|
||||
}
|
||||
setDisassemblyJumpLineAdaptiveSpacingEnabled(true);
|
||||
setDisassemblyJumpLineSpacing(DisassemblyJumpLineSpacingDefault);
|
||||
}
|
||||
|
||||
bool Configuration::getDisassemblyJumpLineAdaptiveSpacingEnabled() const
|
||||
{
|
||||
return s.value(DisassemblyJumpLineAdaptiveSpacingKey, true).toBool();
|
||||
}
|
||||
|
||||
void Configuration::setDisassemblyJumpLineAdaptiveSpacingEnabled(bool enabled)
|
||||
{
|
||||
s.setValue(DisassemblyJumpLineAdaptiveSpacingKey, enabled);
|
||||
}
|
||||
|
||||
int Configuration::getDisassemblyJumpLineSpacing() const
|
||||
{
|
||||
const int spacing
|
||||
= s.value(DisassemblyJumpLineSpacingKey, DisassemblyJumpLineSpacingDefault).toInt();
|
||||
return spacing > 0 ? spacing : DisassemblyJumpLineSpacingDefault;
|
||||
}
|
||||
|
||||
void Configuration::setDisassemblyJumpLineSpacing(int spacing)
|
||||
{
|
||||
s.setValue(DisassemblyJumpLineSpacingKey, qMax(1, spacing));
|
||||
}
|
||||
|
||||
void Configuration::applySavedAsmOptions()
|
||||
@@ -628,13 +954,66 @@ void Configuration::applySavedAsmOptions()
|
||||
}
|
||||
}
|
||||
|
||||
QList<QPair<QString, QString>> Configuration::getAddrBaseOptions()
|
||||
{
|
||||
QStringList values = Core()->cmdList("e asm.addr.base=?");
|
||||
values.removeAll(QString());
|
||||
if (values.isEmpty()) {
|
||||
values = QStringList{"8", "10", "16", "36"};
|
||||
}
|
||||
QList<QPair<QString, QString>> options;
|
||||
for (const QString &v : values) {
|
||||
QString label = v;
|
||||
if (v == QLatin1String("8")) {
|
||||
label = tr("Octal (8)");
|
||||
} else if (v == QLatin1String("10")) {
|
||||
label = tr("Decimal (10)");
|
||||
} else if (v == QLatin1String("16")) {
|
||||
label = tr("Hexadecimal (16)");
|
||||
} else if (v == QLatin1String("36")) {
|
||||
label = tr("Base36 (36)");
|
||||
}
|
||||
options.append({v, label});
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
QString Configuration::getAddrBase()
|
||||
{
|
||||
QString base = getConfigString("asm.addr.base");
|
||||
return base.isEmpty() ? QStringLiteral("16") : base;
|
||||
}
|
||||
|
||||
void Configuration::setAddrBase(const QString &base)
|
||||
{
|
||||
setConfig("asm.addr.base", base);
|
||||
}
|
||||
|
||||
const QList<IaitoInterfaceTheme> &Configuration::iaitoInterfaceThemesList()
|
||||
{
|
||||
static const QList<IaitoInterfaceTheme> list
|
||||
static QList<IaitoInterfaceTheme> list;
|
||||
list
|
||||
= {{"Native", Configuration::nativeWindowIsDark() ? DarkFlag : LightFlag},
|
||||
{"Dark", DarkFlag},
|
||||
{"Midnight", DarkFlag},
|
||||
{"Light", LightFlag}};
|
||||
{"Light", LightFlag},
|
||||
{"Classic", LightFlag},
|
||||
{"Pro", LightFlag},
|
||||
{"Pro Dark", DarkFlag},
|
||||
{"Nord", DarkFlag},
|
||||
{"High Contrast", DarkFlag},
|
||||
{"Cobalt", DarkFlag},
|
||||
{"Capsule", LightFlag},
|
||||
{"Phosphor", DarkFlag}};
|
||||
const auto userThemes
|
||||
= QDir(userThemesDir())
|
||||
.entryInfoList(QStringList{QStringLiteral("*.theme")}, QDir::Files, QDir::Name);
|
||||
for (const QFileInfo &fi : userThemes) {
|
||||
const bool dark = QSettings(fi.absoluteFilePath(), QSettings::IniFormat)
|
||||
.value("meta/dark", true)
|
||||
.toBool();
|
||||
list.append({fi.completeBaseName(), dark ? DarkFlag : LightFlag});
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@@ -775,15 +1154,24 @@ void Configuration::setDecompilerRunInBackground(bool enabled)
|
||||
s.setValue("decompilerRunInBackground", enabled);
|
||||
}
|
||||
|
||||
void Configuration::enableDecompilerAnnotationHighlighter(bool useDecompilerHighlighter)
|
||||
bool Configuration::getDecompilerShowAddresses()
|
||||
{
|
||||
s.setValue("decompilerAnnotationHighlighter", !useDecompilerHighlighter);
|
||||
emit colorsUpdated();
|
||||
return s.value("decompilerShowAddresses", false).toBool();
|
||||
}
|
||||
|
||||
bool Configuration::isDecompilerAnnotationHighlighterEnabled()
|
||||
void Configuration::setDecompilerShowAddresses(bool enabled)
|
||||
{
|
||||
return s.value("decompilerAnnotationHighlighter", true).value<bool>();
|
||||
s.setValue("decompilerShowAddresses", enabled);
|
||||
}
|
||||
|
||||
bool Configuration::getDecompilerShowDisassembly()
|
||||
{
|
||||
return s.value("decompilerShowDisassembly", false).toBool();
|
||||
}
|
||||
|
||||
void Configuration::setDecompilerShowDisassembly(bool enabled)
|
||||
{
|
||||
s.setValue("decompilerShowDisassembly", enabled);
|
||||
}
|
||||
|
||||
bool Configuration::getBitmapTransparentState()
|
||||
@@ -841,3 +1229,13 @@ void Configuration::setGraphBlockEntryOffset(bool enabled)
|
||||
{
|
||||
s.setValue("graphBlockEntryOffset", enabled);
|
||||
}
|
||||
|
||||
bool Configuration::getGraphBlockShadow()
|
||||
{
|
||||
return s.value("graphBlockShadow", true).value<bool>();
|
||||
}
|
||||
|
||||
void Configuration::setGraphBlockShadow(bool enabled)
|
||||
{
|
||||
s.setValue("graphBlockShadow", enabled);
|
||||
}
|
||||
@@ -17,6 +17,7 @@ class Theme;
|
||||
|
||||
class QSyntaxHighlighter;
|
||||
class QTextDocument;
|
||||
class Theme;
|
||||
|
||||
enum ColorFlags {
|
||||
LightFlag = 1,
|
||||
@@ -32,30 +33,58 @@ struct IaitoInterfaceTheme
|
||||
class IAITO_EXPORT Configuration : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum class VisualNavbarLocation { Top = 0, Bottom, Left, Right, SuperTop, SuperBottom };
|
||||
Q_ENUM(VisualNavbarLocation)
|
||||
enum class MemoryScrollBarMode { Spring = 0, Bounded, Hidden };
|
||||
Q_ENUM(MemoryScrollBarMode)
|
||||
|
||||
private:
|
||||
QPalette nativePalette;
|
||||
QFont defaultAppFont;
|
||||
QSettings s;
|
||||
QHash<QString, QColor> m_interfaceChrome;
|
||||
static Configuration *mPtr;
|
||||
|
||||
#ifdef IAITO_ENABLE_KSYNTAXHIGHLIGHTING
|
||||
KSyntaxHighlighting::Repository *kSyntaxHighlightingRepository;
|
||||
#endif
|
||||
bool outputRedirectEnabled = true;
|
||||
// True while the most recent colorsUpdated signal represents only an
|
||||
// interface palette refresh (no radare2 color theme change). Heavy slots
|
||||
// can use isInterfacePaletteOnlyUpdate() to skip expensive content reloads.
|
||||
bool paletteOnlyUpdate = false;
|
||||
// Reentrancy guard to ignore palette change notifications triggered by us.
|
||||
bool applyingTheme = false;
|
||||
|
||||
Configuration();
|
||||
// Colors
|
||||
void loadBaseThemeNative();
|
||||
void loadBaseThemeDark();
|
||||
void loadNativeStylesheet();
|
||||
void loadLightStylesheet();
|
||||
void loadDarkStylesheet();
|
||||
void loadMidnightStylesheet();
|
||||
Theme nativeEngineTheme();
|
||||
bool loadUserStylesheet(const QString &name);
|
||||
|
||||
void onSystemColorSchemeChanged();
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *watched, QEvent *event) override;
|
||||
|
||||
// Asm Options
|
||||
void applySavedAsmOptions();
|
||||
|
||||
public:
|
||||
static const QList<IaitoInterfaceTheme> &iaitoInterfaceThemesList();
|
||||
static QString userThemesDir();
|
||||
static bool isCustomInterfaceTheme(const QString &name);
|
||||
static bool interfaceThemeIsDark(const QString &name);
|
||||
static bool isValidThemeName(const QString &name);
|
||||
static const QStringList &interfaceThemeVariableKeys();
|
||||
static QHash<QString, QColor> defaultInterfaceThemeVariables();
|
||||
static QHash<QString, QColor> loadInterfaceThemeVariables(const QString &name);
|
||||
static void saveInterfaceThemeVariables(
|
||||
const QString &name, const QHash<QString, QColor> &vars, bool dark);
|
||||
void applyInterfaceVariables(const QHash<QString, QColor> &vars, bool dark);
|
||||
void loadEngineTheme(const Theme &theme);
|
||||
static const QHash<QString, ColorFlags> relevantThemes;
|
||||
static const QHash<QString, QHash<ColorFlags, QColor>> iaitoOptionColors;
|
||||
|
||||
@@ -66,10 +95,6 @@ public:
|
||||
|
||||
void resetAll();
|
||||
|
||||
// Auto update
|
||||
bool getAutoUpdateEnabled() const;
|
||||
void setAutoUpdateEnabled(bool au);
|
||||
|
||||
// Languages
|
||||
QLocale getCurrLocale() const;
|
||||
void setLocale(const QLocale &l);
|
||||
@@ -90,13 +115,35 @@ public:
|
||||
* @return the configured font size adjusted by zoom level
|
||||
*/
|
||||
const QFont getFont() const;
|
||||
|
||||
/**
|
||||
* @brief Gets a smaller variant of the configured font, suitable for
|
||||
* list/tree views. Uses 85% of the normal font size.
|
||||
* @return the configured font at reduced size, adjusted by zoom level
|
||||
* unless zoomed is false
|
||||
*/
|
||||
const QFont getSmallFont(bool zoomed = true) const;
|
||||
void setFont(const QFont &font);
|
||||
qreal getZoomFactor() const;
|
||||
void setZoomFactor(qreal zoom);
|
||||
VisualNavbarLocation getVisualNavbarLocation() const;
|
||||
void setVisualNavbarLocation(VisualNavbarLocation location);
|
||||
int getVisualNavbarThickness() const;
|
||||
void setVisualNavbarThickness(int thickness);
|
||||
int getVisualNavbarResolution() const;
|
||||
void setVisualNavbarResolution(int divisor);
|
||||
MemoryScrollBarMode getMemoryScrollBarMode() const;
|
||||
void setMemoryScrollBarMode(MemoryScrollBarMode mode);
|
||||
|
||||
// Colors
|
||||
bool windowColorIsDark();
|
||||
static bool nativeWindowIsDark();
|
||||
/**
|
||||
* @brief Whether the current colorsUpdated signal only represents an
|
||||
* interface palette change, in which case widgets can skip costly
|
||||
* content reloads and just repaint.
|
||||
*/
|
||||
bool isInterfacePaletteOnlyUpdate() const { return paletteOnlyUpdate; }
|
||||
void setLastThemeOf(const IaitoInterfaceTheme &currInterfaceTheme, const QString &theme);
|
||||
QString getLastThemeOf(const IaitoInterfaceTheme &currInterfaceTheme) const;
|
||||
void setInterfaceTheme(int theme);
|
||||
@@ -116,6 +163,9 @@ public:
|
||||
QString getRecentFolder();
|
||||
void setRecentFolder(const QString &dir);
|
||||
|
||||
QString getSha256LookupBaseUrl() const;
|
||||
void setSha256LookupBaseUrl(const QString &url);
|
||||
|
||||
void setNewFileLastClicked(int lastClicked);
|
||||
int getNewFileLastClicked();
|
||||
|
||||
@@ -124,6 +174,10 @@ public:
|
||||
|
||||
// Asm Options
|
||||
void resetToDefaultAsmOptions();
|
||||
bool getDisassemblyJumpLineAdaptiveSpacingEnabled() const;
|
||||
void setDisassemblyJumpLineAdaptiveSpacingEnabled(bool enabled);
|
||||
int getDisassemblyJumpLineSpacing() const;
|
||||
void setDisassemblyJumpLineSpacing(int spacing);
|
||||
|
||||
QString getColorTheme() const { return s.value("theme", "iaito").toString(); }
|
||||
void setColorTheme(const QString &theme);
|
||||
@@ -153,6 +207,20 @@ public:
|
||||
void setConfig(const QString &key, const QVariant &value);
|
||||
bool isFirstExecution();
|
||||
|
||||
/**
|
||||
* @brief Numeric base used to render addresses (asm.addr.base).
|
||||
* @return the current base value (e.g. "8", "10", "16", "36").
|
||||
*/
|
||||
QString getAddrBase();
|
||||
void setAddrBase(const QString &base);
|
||||
/**
|
||||
* @brief Ordered (value, label) pairs for the available address bases.
|
||||
*
|
||||
* Queried from radare2 via "e asm.addr.base=?", falling back to the
|
||||
* known set (8/10/16/36) when the option is unavailable.
|
||||
*/
|
||||
static QList<QPair<QString, QString>> getAddrBaseOptions();
|
||||
|
||||
/**
|
||||
* @return id of the last selected decompiler (see
|
||||
* IaitoCore::getDecompilerById)
|
||||
@@ -166,9 +234,13 @@ public:
|
||||
// Decompiler execution mode
|
||||
bool getDecompilerRunInBackground();
|
||||
void setDecompilerRunInBackground(bool enabled);
|
||||
bool getDecompilerShowAddresses();
|
||||
void setDecompilerShowAddresses(bool enabled);
|
||||
bool getDecompilerShowDisassembly();
|
||||
void setDecompilerShowDisassembly(bool enabled);
|
||||
|
||||
void enableDecompilerAnnotationHighlighter(bool useDecompilerHighlighter);
|
||||
bool isDecompilerAnnotationHighlighterEnabled();
|
||||
bool getAddressRangeSelectionSyncEnabled() const;
|
||||
void setAddressRangeSelectionSyncEnabled(bool enabled);
|
||||
|
||||
// Graph
|
||||
int getGraphBlockMaxChars() const { return s.value("graph.maxcols", 100).toInt(); }
|
||||
@@ -201,6 +273,9 @@ public:
|
||||
*/
|
||||
void setGraphBlockEntryOffset(bool enabled);
|
||||
|
||||
bool getGraphBlockShadow();
|
||||
void setGraphBlockShadow(bool enabled);
|
||||
|
||||
/**
|
||||
* @brief Enable or disable Iaito output redirection.
|
||||
* Output redirection state can only be changed early during Iaito
|
||||
@@ -215,7 +290,18 @@ public slots:
|
||||
signals:
|
||||
void fontsUpdated();
|
||||
void colorsUpdated();
|
||||
/**
|
||||
* @brief Emitted in addition to colorsUpdated when only the interface
|
||||
* palette changed (no radare2 color theme switch). Listeners can use
|
||||
* this as a lightweight hint to repaint without re-fetching content.
|
||||
*/
|
||||
void interfacePaletteUpdated();
|
||||
void interfaceThemeChanged();
|
||||
void visualNavbarLocationChanged(VisualNavbarLocation location);
|
||||
void visualNavbarThicknessChanged(int thickness);
|
||||
void visualNavbarResolutionChanged(int divisor);
|
||||
void memoryScrollBarModeChanged(MemoryScrollBarMode mode);
|
||||
void addressRangeSelectionSyncEnabledChanged(bool enabled);
|
||||
#ifdef IAITO_ENABLE_KSYNTAXHIGHLIGHTING
|
||||
void kSyntaxHighlightingThemeChanged();
|
||||
#endif
|
||||
|
||||
@@ -112,8 +112,10 @@ void showCrashDialog(const QString &dumpFile)
|
||||
{
|
||||
QMessageBox mb;
|
||||
mb.setWindowTitle(QObject::tr("Crash"));
|
||||
mb.setText(QObject::tr("Iaito received a signal it can't handle and will close.<br/>"
|
||||
"Would you like to create a crash dump for a bug report?"));
|
||||
mb.setText(
|
||||
QObject::tr(
|
||||
"Iaito received a signal it can't handle and will close.<br/>"
|
||||
"Would you like to create a crash dump for a bug report?"));
|
||||
mb.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
|
||||
mb.button(QMessageBox::Yes)->setText(QObject::tr("Create a Crash Dump"));
|
||||
mb.button(QMessageBox::No)->setText(QObject::tr("Quit"));
|
||||
@@ -138,6 +140,7 @@ void showCrashDialog(const QString &dumpFile)
|
||||
QObject::tr("Minidump (*.dmp)"));
|
||||
|
||||
if (dumpSaveFileName.isEmpty()) {
|
||||
QFile(dumpFile).remove();
|
||||
return;
|
||||
}
|
||||
if (QFile::rename(dumpFile, dumpSaveFileName)) {
|
||||
@@ -147,17 +150,19 @@ void showCrashDialog(const QString &dumpFile)
|
||||
QMessageBox::critical(
|
||||
nullptr,
|
||||
QObject::tr("Save Crash Dump"),
|
||||
QObject::tr("Failed to write to %1.<br/>"
|
||||
"Please make sure you have access to that directory "
|
||||
"and try again.")
|
||||
QObject::tr(
|
||||
"Failed to write to %1.<br/>"
|
||||
"Please make sure you have access to that directory "
|
||||
"and try again.")
|
||||
.arg(QFileInfo(dumpSaveFileName).dir().path()));
|
||||
} while (true);
|
||||
|
||||
if (ok) {
|
||||
QMessageBox info;
|
||||
info.setWindowTitle(QObject::tr("Success"));
|
||||
info.setText(QObject::tr("<a href=\"%1\">Crash dump</a> was successfully created.")
|
||||
.arg(QFileInfo(dumpSaveFileName).dir().path()));
|
||||
info.setText(
|
||||
QObject::tr("<a href=\"%1\">Crash dump</a> was successfully created.")
|
||||
.arg(QFileInfo(dumpSaveFileName).dir().path()));
|
||||
info.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
|
||||
|
||||
info.button(QMessageBox::Yes)->setText(QObject::tr("Open an Issue"));
|
||||
@@ -169,6 +174,7 @@ void showCrashDialog(const QString &dumpFile)
|
||||
openIssue();
|
||||
}
|
||||
} else {
|
||||
QFile(dumpFile).remove();
|
||||
QMessageBox::critical(
|
||||
nullptr,
|
||||
QObject::tr("Error"),
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
#include "DataAnalysis.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
namespace DataAnalysis {
|
||||
|
||||
Range sizedRange(RVA start, RVA size)
|
||||
{
|
||||
return {start, start + qMin(size, RVA_MAX - start)};
|
||||
}
|
||||
|
||||
Range intersect(Range a, Range b)
|
||||
{
|
||||
const RVA start = qMax(a.start, b.start);
|
||||
return {start, qMax(start, qMin(a.end, b.end))};
|
||||
}
|
||||
|
||||
Range bucket(Range range, int index, int count)
|
||||
{
|
||||
if (!range.valid() || count <= 0 || index < 0 || index >= count) {
|
||||
return {};
|
||||
}
|
||||
// Avoid both floating point address rounding and multiplication overflow.
|
||||
auto boundary = [range, count](RVA i) {
|
||||
return range.start + (range.size() / count) * i + (range.size() % count) * i / count;
|
||||
};
|
||||
return {boundary(index), boundary(index + 1)};
|
||||
}
|
||||
|
||||
Range centered(Range domain, RVA address, RVA size)
|
||||
{
|
||||
if (!domain.valid()) {
|
||||
return {};
|
||||
}
|
||||
size = qBound<RVA>(1, size, domain.size());
|
||||
address = qBound(domain.start, address, domain.end - 1);
|
||||
const RVA start = address - qMin(address - domain.start, size / 2);
|
||||
const RVA clamped = qMin(start, domain.end - size);
|
||||
return {clamped, clamped + size};
|
||||
}
|
||||
|
||||
QVector<Range> mergeRanges(QVector<Range> ranges)
|
||||
{
|
||||
std::sort(ranges.begin(), ranges.end(), [](Range a, Range b) { return a.start < b.start; });
|
||||
QVector<Range> result;
|
||||
for (auto range : ranges) {
|
||||
if (!range.valid()) {
|
||||
continue;
|
||||
}
|
||||
if (result.isEmpty() || result.last().end < range.start) {
|
||||
result.append(range);
|
||||
} else {
|
||||
result.last().end = qMax(result.last().end, range.end);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Sample sample(
|
||||
Range range,
|
||||
const QVector<Range> &readable,
|
||||
const std::function<QByteArray(RVA, int)> &read,
|
||||
int budget)
|
||||
{
|
||||
Sample result;
|
||||
result.range = range;
|
||||
if (!range.valid() || budget <= 0) {
|
||||
return result;
|
||||
}
|
||||
QVector<Range> spans;
|
||||
for (auto candidate : readable) {
|
||||
const Range span = intersect(range, candidate);
|
||||
if (span.valid()) {
|
||||
spans.append(span);
|
||||
result.covered += span.size();
|
||||
}
|
||||
}
|
||||
std::array<int, 256> histogram{};
|
||||
RVA remaining = result.covered;
|
||||
for (auto span : spans) {
|
||||
if (budget <= 0) {
|
||||
break;
|
||||
}
|
||||
// Allocate the budget proportionally among disjoint readable spans.
|
||||
const int share = qMax(1, int((long double) span.size() / remaining * budget));
|
||||
const int length = int(qMin<RVA>(span.size(), qMin(budget, share)));
|
||||
const RVA address = span.start + (span.size() - length) / 2;
|
||||
const QByteArray data = read(address, length).left(length);
|
||||
for (unsigned char byte : data) {
|
||||
++histogram[byte];
|
||||
}
|
||||
result.bytes += data.size();
|
||||
budget -= length;
|
||||
remaining -= span.size();
|
||||
}
|
||||
if (!result.bytes) {
|
||||
return result;
|
||||
}
|
||||
auto &v = result.values;
|
||||
for (int byte = 0; byte < 256; ++byte) {
|
||||
const int n = histogram[byte];
|
||||
if (!n) {
|
||||
continue;
|
||||
}
|
||||
const double p = double(n) / result.bytes;
|
||||
v[Entropy] -= p * std::log2(p) / 8.0;
|
||||
v[Printable] += (byte >= 32 && byte <= 126) ? p : 0;
|
||||
v[Zero] += byte == 0 ? p : 0;
|
||||
v[FF] += byte == 255 ? p : 0;
|
||||
v[Unique] += 1.0 / 256;
|
||||
v[Mean] += p * byte / 255;
|
||||
v[Alpha] += ((byte >= 'a' && byte <= 'z') || (byte >= 'A' && byte <= 'Z')) ? p : 0;
|
||||
v[Digit] += (byte >= '0' && byte <= '9') ? p : 0;
|
||||
v[Space] += (byte == ' ' || (byte >= 9 && byte <= 13)) ? p : 0;
|
||||
v[Control] += (byte < 32 || byte == 127) ? p : 0;
|
||||
v[High] += byte >= 128 ? p : 0;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace DataAnalysis
|
||||
@@ -0,0 +1,64 @@
|
||||
#ifndef DATAANALYSIS_H
|
||||
#define DATAANALYSIS_H
|
||||
|
||||
#include "core/IaitoCommon.h"
|
||||
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <QByteArray>
|
||||
#include <QVector>
|
||||
|
||||
namespace DataAnalysis {
|
||||
|
||||
// Half-open ranges, including when the exclusive end is RVA_MAX.
|
||||
struct Range
|
||||
{
|
||||
RVA start = 0;
|
||||
RVA end = 0;
|
||||
bool valid() const { return start < end; }
|
||||
RVA size() const { return end - start; }
|
||||
bool contains(RVA address) const { return start <= address && address < end; }
|
||||
bool operator==(const Range &other) const { return start == other.start && end == other.end; }
|
||||
bool operator!=(const Range &other) const { return !(*this == other); }
|
||||
};
|
||||
|
||||
enum Metric {
|
||||
Entropy,
|
||||
Printable,
|
||||
Zero,
|
||||
FF,
|
||||
Unique,
|
||||
Mean,
|
||||
Alpha,
|
||||
Digit,
|
||||
Space,
|
||||
Control,
|
||||
High,
|
||||
Count
|
||||
};
|
||||
|
||||
struct Sample
|
||||
{
|
||||
Range range;
|
||||
RVA covered = 0;
|
||||
int bytes = 0;
|
||||
std::array<double, Count> values{}; // Normalized to [0, 1]; entropy / 8.
|
||||
};
|
||||
|
||||
Range sizedRange(RVA start, RVA size);
|
||||
Range intersect(Range a, Range b);
|
||||
Range bucket(Range range, int index, int count);
|
||||
Range centered(Range domain, RVA address, RVA size);
|
||||
QVector<Range> mergeRanges(QVector<Range> ranges);
|
||||
|
||||
// Bounded, deterministic sampling. Reads only known readable spans; missing bytes
|
||||
// never contribute fabricated 0xff data. Large buckets use centered windows.
|
||||
Sample sample(
|
||||
Range range,
|
||||
const QVector<Range> &readable,
|
||||
const std::function<QByteArray(RVA, int)> &read,
|
||||
int budget = 4096);
|
||||
|
||||
} // namespace DataAnalysis
|
||||
|
||||
#endif
|
||||
@@ -4,14 +4,16 @@
|
||||
|
||||
#include <QDebug>
|
||||
#include <QMetaObject>
|
||||
#include <QThread>
|
||||
|
||||
static bool loopInterrupted = false;
|
||||
DecompileTask::DecompileTask(Decompiler *decompiler, RVA addr, QObject *parent)
|
||||
: AsyncTask()
|
||||
, decompiler(decompiler)
|
||||
, addr(addr)
|
||||
, code(nullptr)
|
||||
{}
|
||||
{
|
||||
setParent(parent);
|
||||
}
|
||||
|
||||
DecompileTask::~DecompileTask() {}
|
||||
|
||||
@@ -19,16 +21,15 @@ void DecompileTask::interrupt()
|
||||
{
|
||||
loopInterrupted = true;
|
||||
AsyncTask::interrupt();
|
||||
#if R2_VERSION_NUMBER >= 50909
|
||||
RCore *core = Core()->core_;
|
||||
if (core && core->cons && core->cons->context) {
|
||||
core->cons->context->breaked = true;
|
||||
if (decompiler) {
|
||||
if (decompiler->thread() == QThread::currentThread()) {
|
||||
decompiler->cancel();
|
||||
} else {
|
||||
QMetaObject::invokeMethod(
|
||||
decompiler, [this]() { decompiler->cancel(); }, Qt::QueuedConnection);
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (r_cons_singleton() && r_cons_singleton()->context) {
|
||||
r_cons_singleton()->context->breaked = true;
|
||||
}
|
||||
#endif
|
||||
Core()->setConsBreaked();
|
||||
// Previously we raised SIGINT in-process here which can interrupt
|
||||
// Qt/libc allocations and lead to crashes. Instead rely on radare2's
|
||||
// break flag plus signaling child processes from AsyncTask::interrupt.
|
||||
@@ -45,7 +46,8 @@ void DecompileTask::runTask()
|
||||
return;
|
||||
}
|
||||
// Respect user setting: when background mode is disabled (default), run synchronously.
|
||||
const bool runSync = !Config()->getDecompilerRunInBackground();
|
||||
const bool runSync = !Config()->getDecompilerRunInBackground()
|
||||
&& !decompiler->prefersBackground();
|
||||
if (runSync) {
|
||||
// Some decompilers and core commands expect to run on the main (GUI) thread
|
||||
// to properly collect output. Execute the synchronous decompilation on the
|
||||
@@ -57,8 +59,7 @@ void DecompileTask::runTask()
|
||||
[&result, this]() { result = decompiler->decompileSync(addr); },
|
||||
Qt::BlockingQueuedConnection);
|
||||
if (!invoked) {
|
||||
code = Decompiler::makeWarning(
|
||||
QObject::tr("Failed to invoke decompiler synchronously"));
|
||||
code = Decompiler::makeWarning(QObject::tr("Failed to invoke decompiler synchronously"));
|
||||
} else {
|
||||
code = result;
|
||||
}
|
||||
@@ -108,7 +109,10 @@ void DecompileTask::runTask()
|
||||
if (loopInterrupted) {
|
||||
eprintf("DecompilerTask was interrupted\n");
|
||||
loopInterrupted = false;
|
||||
return;
|
||||
if (!resultCode) {
|
||||
code = Decompiler::makeWarning(QObject::tr("Decompilation cancelled"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Store resulting code (or a warning if null). To avoid potential
|
||||
// use-after-free or allocator/lifetime issues caused by R2 internals
|
||||
@@ -116,23 +120,25 @@ void DecompileTask::runTask()
|
||||
// received and free the original.
|
||||
if (resultCode) {
|
||||
RCodeMeta *copy = r_codemeta_new("");
|
||||
free(copy->code);
|
||||
if (resultCode->code) {
|
||||
copy->code = strdup(resultCode->code);
|
||||
} else {
|
||||
copy->code = strdup("");
|
||||
}
|
||||
// Copy annotations safely (only essential fields)
|
||||
// Copy annotations safely
|
||||
#if R2_ABIVERSION >= 40
|
||||
RCodeMetaItem *oldItem;
|
||||
R_VEC_FOREACH(&resultCode->annotations, oldItem)
|
||||
{
|
||||
#else
|
||||
void *iter;
|
||||
r_vector_foreach(&resultCode->annotations, iter)
|
||||
{
|
||||
RCodeMetaItem *oldItem = (RCodeMetaItem *) iter;
|
||||
RCodeMetaItem *oldItem = reinterpret_cast<RCodeMetaItem *>(iter);
|
||||
#endif
|
||||
RCodeMetaItem *newItem = r_codemeta_item_new();
|
||||
newItem->start = oldItem->start;
|
||||
newItem->end = oldItem->end;
|
||||
newItem->type = oldItem->type;
|
||||
if (newItem->type == R_CODEMETA_TYPE_OFFSET) {
|
||||
newItem->offset.offset = oldItem->offset.offset;
|
||||
}
|
||||
r_codemeta_item_copy(newItem, oldItem);
|
||||
r_codemeta_add_item(copy, newItem);
|
||||
}
|
||||
// Free original and keep our copy
|
||||
|
||||
@@ -26,6 +26,7 @@ private:
|
||||
Decompiler *decompiler;
|
||||
RVA addr;
|
||||
RCodeMeta *code;
|
||||
bool loopInterrupted = false;
|
||||
};
|
||||
|
||||
#endif // DECOMPILETASK_H
|
||||
@@ -1,5 +1,7 @@
|
||||
|
||||
#include "Decompiler.h"
|
||||
#include "CodeMetaRange.h"
|
||||
#include "Configuration.h"
|
||||
#include "Iaito.h"
|
||||
|
||||
#include <QJsonArray>
|
||||
@@ -17,6 +19,242 @@ RCodeMeta *Decompiler::makeWarning(QString warningMessage)
|
||||
return r_codemeta_new(temporary.c_str());
|
||||
}
|
||||
|
||||
QStringList Decompiler::listOptions()
|
||||
{
|
||||
QString prefix = getConfigPrefix();
|
||||
if (prefix.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
QStringList result;
|
||||
QStringList lines = Core()->cmdList(QString("e %1.").arg(prefix).toUtf8().constData());
|
||||
QString keyPrefix = prefix + QLatin1Char('.');
|
||||
for (const QString &line : lines) {
|
||||
QString trimmed = line.trimmed();
|
||||
if (trimmed.isEmpty() || !trimmed.startsWith(keyPrefix)) {
|
||||
continue;
|
||||
}
|
||||
result.append(trimmed.mid(keyPrefix.length()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QString Decompiler::getOption(const QString &key)
|
||||
{
|
||||
QString prefix = getConfigPrefix();
|
||||
if (prefix.isEmpty() || key.isEmpty()) {
|
||||
return QString();
|
||||
}
|
||||
return Config()->getConfigString(prefix + QLatin1Char('.') + key);
|
||||
}
|
||||
|
||||
void Decompiler::setOption(const QString &key, const QString &value)
|
||||
{
|
||||
QString prefix = getConfigPrefix();
|
||||
if (prefix.isEmpty() || key.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Config()->setConfig(prefix + QLatin1Char('.') + key, value);
|
||||
}
|
||||
|
||||
static RSyntaxHighlightType syntaxHighlightTypeFromString(const QString &str)
|
||||
{
|
||||
struct SyntaxHighlightType
|
||||
{
|
||||
const char *name;
|
||||
RSyntaxHighlightType type;
|
||||
};
|
||||
static const SyntaxHighlightType types[] = {
|
||||
{"keyword", R_SYNTAX_HIGHLIGHT_TYPE_KEYWORD},
|
||||
{"comment", R_SYNTAX_HIGHLIGHT_TYPE_COMMENT},
|
||||
{"datatype", R_SYNTAX_HIGHLIGHT_TYPE_DATATYPE},
|
||||
{"function_name", R_SYNTAX_HIGHLIGHT_TYPE_FUNCTION_NAME},
|
||||
{"function_parameter", R_SYNTAX_HIGHLIGHT_TYPE_FUNCTION_PARAMETER},
|
||||
{"local_variable", R_SYNTAX_HIGHLIGHT_TYPE_LOCAL_VARIABLE},
|
||||
{"constant_variable", R_SYNTAX_HIGHLIGHT_TYPE_CONSTANT_VARIABLE},
|
||||
{"global_variable", R_SYNTAX_HIGHLIGHT_TYPE_GLOBAL_VARIABLE},
|
||||
};
|
||||
|
||||
for (const SyntaxHighlightType &type : types) {
|
||||
if (str == QLatin1String(type.name)) {
|
||||
return type.type;
|
||||
}
|
||||
}
|
||||
return R_SYNTAX_HIGHLIGHT_TYPE_KEYWORD;
|
||||
}
|
||||
|
||||
static char *jsonStringDup(const QJsonObject &obj, const QString &key)
|
||||
{
|
||||
const QByteArray bytes = obj.value(key).toString().toUtf8();
|
||||
return strdup(bytes.constData());
|
||||
}
|
||||
|
||||
static bool setCodeMetaAddress(RCodeMetaItem *mi, const QJsonObject &obj, RCodeMetaItemType type)
|
||||
{
|
||||
auto offset = codeMetaAddressFromJson(obj.value(QStringLiteral("offset")));
|
||||
if (!offset) {
|
||||
return false;
|
||||
}
|
||||
mi->type = type;
|
||||
mi->reference.offset = *offset;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void addCodeMetaAnnotation(
|
||||
RCodeMeta *code,
|
||||
const QJsonObject &obj,
|
||||
const QString &codeString,
|
||||
R2JsonDecompiler::AnnotationMode annotationMode)
|
||||
{
|
||||
const QString type = obj.value(QStringLiteral("type")).toString();
|
||||
const bool isOffset = type == QLatin1String("offset");
|
||||
if (!isOffset && annotationMode == R2JsonDecompiler::AnnotationMode::OffsetsOnly) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto range = isOffset ? codeMetaOffsetRangeFromJson(obj, codeString)
|
||||
: codeMetaRangeFromJson(obj, static_cast<size_t>(codeString.size()));
|
||||
if (!range) {
|
||||
return;
|
||||
}
|
||||
|
||||
RCodeMetaItem *mi = r_codemeta_item_new();
|
||||
if (!mi) {
|
||||
return;
|
||||
}
|
||||
mi->start = range->start;
|
||||
mi->end = range->end;
|
||||
|
||||
if (isOffset) {
|
||||
auto offset = codeMetaAddressFromJson(obj.value(QStringLiteral("offset")));
|
||||
if (!offset) {
|
||||
r_codemeta_item_free(mi);
|
||||
return;
|
||||
}
|
||||
mi->type = R_CODEMETA_TYPE_OFFSET;
|
||||
mi->offset.offset = *offset;
|
||||
} else if (type == QLatin1String("syntax_highlight")) {
|
||||
mi->type = R_CODEMETA_TYPE_SYNTAX_HIGHLIGHT;
|
||||
mi->syntax_highlight.type = syntaxHighlightTypeFromString(
|
||||
obj.value(QStringLiteral("syntax_highlight")).toString());
|
||||
} else if (type == QLatin1String("function_name")) {
|
||||
if (!setCodeMetaAddress(mi, obj, R_CODEMETA_TYPE_FUNCTION_NAME)) {
|
||||
r_codemeta_item_free(mi);
|
||||
return;
|
||||
}
|
||||
mi->reference.name = jsonStringDup(obj, QStringLiteral("name"));
|
||||
} else if (type == QLatin1String("global_variable")) {
|
||||
if (!setCodeMetaAddress(mi, obj, R_CODEMETA_TYPE_GLOBAL_VARIABLE)) {
|
||||
r_codemeta_item_free(mi);
|
||||
return;
|
||||
}
|
||||
} else if (type == QLatin1String("constant_variable")) {
|
||||
if (!setCodeMetaAddress(mi, obj, R_CODEMETA_TYPE_CONSTANT_VARIABLE)) {
|
||||
r_codemeta_item_free(mi);
|
||||
return;
|
||||
}
|
||||
} else if (type == QLatin1String("local_variable")) {
|
||||
mi->type = R_CODEMETA_TYPE_LOCAL_VARIABLE;
|
||||
mi->variable.name = jsonStringDup(obj, QStringLiteral("name"));
|
||||
} else if (type == QLatin1String("function_parameter")) {
|
||||
mi->type = R_CODEMETA_TYPE_FUNCTION_PARAMETER;
|
||||
mi->variable.name = jsonStringDup(obj, QStringLiteral("name"));
|
||||
} else {
|
||||
r_codemeta_item_free(mi);
|
||||
return;
|
||||
}
|
||||
|
||||
r_codemeta_add_item(code, mi);
|
||||
}
|
||||
|
||||
static void addCodeMetaAnnotations(
|
||||
RCodeMeta *code,
|
||||
const QJsonArray &annotations,
|
||||
const QString &codeString,
|
||||
R2JsonDecompiler::AnnotationMode annotationMode)
|
||||
{
|
||||
for (const QJsonValue &line : annotations) {
|
||||
const QJsonObject obj = line.toObject();
|
||||
if (!obj.isEmpty()) {
|
||||
addCodeMetaAnnotation(code, obj, codeString, annotationMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void appendJsonErrors(QString &codeString, const QJsonArray &errors)
|
||||
{
|
||||
for (const QJsonValue &line : errors) {
|
||||
if (line.isString()) {
|
||||
codeString.append(line.toString());
|
||||
codeString.append(QLatin1Char('\n'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
R2JsonDecompiler::R2JsonDecompiler(
|
||||
const QString &id,
|
||||
const QString &name,
|
||||
const QString &command,
|
||||
const QString &jsonName,
|
||||
AnnotationMode annotationMode,
|
||||
QObject *parent)
|
||||
: Decompiler(id, name, parent)
|
||||
, command(command)
|
||||
, jsonName(jsonName)
|
||||
, annotationMode(annotationMode)
|
||||
{}
|
||||
|
||||
QString R2JsonDecompiler::decompileCommand(RVA addr) const
|
||||
{
|
||||
return command + QStringLiteral(" @ ") + QString::number(addr);
|
||||
}
|
||||
|
||||
RCodeMeta *R2JsonDecompiler::buildCodeMeta(const QJsonObject &json) const
|
||||
{
|
||||
const QString codeString = json.value(QStringLiteral("code")).toString();
|
||||
QString output = codeString;
|
||||
appendJsonErrors(output, json.value(QStringLiteral("errors")).toArray());
|
||||
|
||||
const QByteArray bytes = output.toUtf8();
|
||||
RCodeMeta *code = r_codemeta_new(bytes.constData());
|
||||
if (!code) {
|
||||
return nullptr;
|
||||
}
|
||||
addCodeMetaAnnotations(
|
||||
code, json.value(QStringLiteral("annotations")).toArray(), codeString, annotationMode);
|
||||
return code;
|
||||
}
|
||||
|
||||
RCodeMeta *R2JsonDecompiler::decompileSync(RVA addr)
|
||||
{
|
||||
const QJsonObject json = Core()->cmdj(decompileCommand(addr)).object();
|
||||
if (json.isEmpty()) {
|
||||
return nullptr;
|
||||
}
|
||||
return buildCodeMeta(json);
|
||||
}
|
||||
|
||||
void R2JsonDecompiler::decompileAt(RVA addr)
|
||||
{
|
||||
if (task) {
|
||||
return;
|
||||
}
|
||||
|
||||
task = new R2Task(decompileCommand(addr));
|
||||
connect(task, &R2Task::finished, this, [this]() {
|
||||
const QJsonObject json = task->getResultJson().object();
|
||||
delete task;
|
||||
task = nullptr;
|
||||
if (json.isEmpty()) {
|
||||
emit finished(Decompiler::makeWarning(tr("Failed to parse JSON from %1").arg(jsonName)));
|
||||
return;
|
||||
}
|
||||
RCodeMeta *code = buildCodeMeta(json);
|
||||
emit finished(
|
||||
code ? code : Decompiler::makeWarning(tr("Failed to build decompiler output")));
|
||||
});
|
||||
task->startTask();
|
||||
}
|
||||
|
||||
R2DecDecompiler::R2DecDecompiler(QObject *parent)
|
||||
: Decompiler("r2dec", "r2dec", parent)
|
||||
{
|
||||
@@ -55,13 +293,17 @@ RCodeMeta *R2DecDecompiler::decompileSync(RVA addr)
|
||||
if (lineObject.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
RCodeMetaItem *mi = r_codemeta_item_new();
|
||||
mi->start = codeString.length();
|
||||
auto offset = codeMetaAddressFromJson(lineObject["offset"]);
|
||||
size_t start = codeString.length();
|
||||
codeString.append(lineObject["str"].toString() + "\n");
|
||||
if (!offset) {
|
||||
continue;
|
||||
}
|
||||
RCodeMetaItem *mi = r_codemeta_item_new();
|
||||
mi->start = start;
|
||||
mi->end = codeString.length();
|
||||
bool ok;
|
||||
mi->type = R_CODEMETA_TYPE_OFFSET;
|
||||
mi->offset.offset = lineObject["offset"].toVariant().toULongLong(&ok);
|
||||
mi->offset.offset = *offset;
|
||||
r_codemeta_add_item(code, mi);
|
||||
}
|
||||
|
||||
@@ -106,13 +348,17 @@ void R2DecDecompiler::decompileAt(RVA addr)
|
||||
if (lineObject.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
RCodeMetaItem *mi = r_codemeta_item_new();
|
||||
mi->start = codeString.length();
|
||||
auto offset = codeMetaAddressFromJson(lineObject["offset"]);
|
||||
size_t start = codeString.length();
|
||||
codeString.append(lineObject["str"].toString() + "\n");
|
||||
if (!offset) {
|
||||
continue;
|
||||
}
|
||||
RCodeMetaItem *mi = r_codemeta_item_new();
|
||||
mi->start = start;
|
||||
mi->end = codeString.length();
|
||||
bool ok;
|
||||
mi->type = R_CODEMETA_TYPE_OFFSET;
|
||||
mi->offset.offset = lineObject["offset"].toVariant().toULongLong(&ok);
|
||||
mi->offset.offset = *offset;
|
||||
r_codemeta_add_item(code, mi);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
class QJsonObject;
|
||||
|
||||
/**
|
||||
* Implements a decompiler that can be registered using
|
||||
* IaitoCore::registerDecompiler()
|
||||
@@ -29,6 +31,18 @@ public:
|
||||
QString getName() const { return name; }
|
||||
virtual bool isRunning() { return false; }
|
||||
virtual bool isCancelable() { return false; }
|
||||
virtual bool prefersBackground() const { return false; }
|
||||
virtual bool requiresManualTrigger() const { return false; }
|
||||
virtual bool hasCachedResult(RVA addr) const
|
||||
{
|
||||
Q_UNUSED(addr);
|
||||
return false;
|
||||
}
|
||||
virtual QString getConfigPrefix() const { return QString(); }
|
||||
|
||||
virtual QStringList listOptions();
|
||||
virtual QString getOption(const QString &key);
|
||||
virtual void setOption(const QString &key, const QString &value);
|
||||
|
||||
virtual void decompileAt(RVA addr) = 0;
|
||||
virtual RCodeMeta *decompileSync(RVA addr) = 0;
|
||||
@@ -38,6 +52,36 @@ signals:
|
||||
void finished(RCodeMeta *codeDecompiled);
|
||||
};
|
||||
|
||||
class IAITO_EXPORT R2JsonDecompiler : public Decompiler
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum class AnnotationMode { OffsetsOnly, Full };
|
||||
|
||||
R2JsonDecompiler(
|
||||
const QString &id,
|
||||
const QString &name,
|
||||
const QString &command,
|
||||
const QString &jsonName,
|
||||
AnnotationMode annotationMode,
|
||||
QObject *parent = nullptr);
|
||||
|
||||
RCodeMeta *decompileSync(RVA addr) override;
|
||||
void decompileAt(RVA addr) override;
|
||||
bool isRunning() override { return task != nullptr; }
|
||||
|
||||
protected:
|
||||
virtual RCodeMeta *buildCodeMeta(const QJsonObject &json) const;
|
||||
QString decompileCommand(RVA addr) const;
|
||||
|
||||
private:
|
||||
R2Task *task = nullptr;
|
||||
const QString command;
|
||||
const QString jsonName;
|
||||
const AnnotationMode annotationMode;
|
||||
};
|
||||
|
||||
class R2DecDecompiler : public Decompiler
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -51,6 +95,7 @@ public:
|
||||
void decompileAt(RVA addr) override;
|
||||
|
||||
bool isRunning() override { return task != nullptr; }
|
||||
QString getConfigPrefix() const override { return QStringLiteral("r2dec"); }
|
||||
|
||||
static bool isAvailable();
|
||||
};
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
|
||||
#include "DecompilerHighlighter.h"
|
||||
#ifndef IAITO_DECOMPILER_CACHE_HIGHLIGHTS
|
||||
#include "CodeMetaRange.h"
|
||||
#endif
|
||||
#include "common/Configuration.h"
|
||||
|
||||
#ifdef IAITO_DECOMPILER_CACHE_HIGHLIGHTS
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#endif
|
||||
#include <limits>
|
||||
|
||||
DecompilerHighlighter::DecompilerHighlighter(QTextDocument *parent)
|
||||
: QSyntaxHighlighter(parent)
|
||||
{
|
||||
@@ -15,8 +24,59 @@ DecompilerHighlighter::DecompilerHighlighter(QTextDocument *parent)
|
||||
void DecompilerHighlighter::setAnnotations(RCodeMeta *code)
|
||||
{
|
||||
this->code = code;
|
||||
#ifdef IAITO_DECOMPILER_CACHE_HIGHLIGHTS
|
||||
rebuildHighlightItems();
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef IAITO_DECOMPILER_CACHE_HIGHLIGHTS
|
||||
void DecompilerHighlighter::rebuildHighlightItems()
|
||||
{
|
||||
highlightItems.clear();
|
||||
if (!code || !code->code) {
|
||||
return;
|
||||
}
|
||||
const size_t len = strlen(code->code);
|
||||
auto cm = r_codemeta_in(code, 0, len + 1);
|
||||
if (!cm) {
|
||||
return;
|
||||
}
|
||||
auto process = [&](RCodeMetaItem *a) {
|
||||
if (!a || a->type != R_CODEMETA_TYPE_SYNTAX_HIGHLIGHT) {
|
||||
return;
|
||||
}
|
||||
const int t = static_cast<int>(a->syntax_highlight.type);
|
||||
if (t < 0 || t >= HIGHLIGHT_COUNT) {
|
||||
return;
|
||||
}
|
||||
if (a->end <= a->start) {
|
||||
return;
|
||||
}
|
||||
highlightItems.push_back({a->start, a->end, t});
|
||||
};
|
||||
#if R2_ABIVERSION >= 40
|
||||
std::unique_ptr<RVecCodeMetaItemPtr, decltype(&RVecCodeMetaItemPtr_free)>
|
||||
annotations(cm, &RVecCodeMetaItemPtr_free);
|
||||
RCodeMetaItem **iter;
|
||||
R_VEC_FOREACH(annotations.get(), iter)
|
||||
{
|
||||
process(*iter);
|
||||
}
|
||||
#else
|
||||
std::unique_ptr<RPVector, decltype(&r_pvector_free)> annotations(cm, &r_pvector_free);
|
||||
void **iter;
|
||||
r_pvector_foreach(annotations.get(), iter)
|
||||
{
|
||||
process(static_cast<RCodeMetaItem *>(*iter));
|
||||
}
|
||||
#endif
|
||||
std::sort(
|
||||
highlightItems.begin(),
|
||||
highlightItems.end(),
|
||||
[](const HighlightItem &a, const HighlightItem &b) { return a.start < b.start; });
|
||||
}
|
||||
#endif
|
||||
|
||||
void DecompilerHighlighter::setupTheme()
|
||||
{
|
||||
struct
|
||||
@@ -41,37 +101,84 @@ void DecompilerHighlighter::setupTheme()
|
||||
|
||||
void DecompilerHighlighter::highlightBlock(const QString &)
|
||||
{
|
||||
#ifdef IAITO_DECOMPILER_CACHE_HIGHLIGHTS
|
||||
if (highlightItems.empty()) {
|
||||
return;
|
||||
}
|
||||
auto block = currentBlock();
|
||||
const size_t start = static_cast<size_t>(block.position());
|
||||
const size_t end = start + static_cast<size_t>(block.length());
|
||||
|
||||
// Upper bound: first cached item whose start is >= block end.
|
||||
// Everything past this point cannot overlap the block.
|
||||
auto itEnd = std::lower_bound(
|
||||
highlightItems.begin(),
|
||||
highlightItems.end(),
|
||||
end,
|
||||
[](const HighlightItem &item, size_t value) { return item.start < value; });
|
||||
|
||||
for (auto it = highlightItems.begin(); it != itEnd; ++it) {
|
||||
if (it->end <= start) {
|
||||
continue; // entirely before block
|
||||
}
|
||||
const size_t rangeStart = std::max(start, it->start);
|
||||
const size_t rangeEnd = std::min(end, it->end);
|
||||
if (rangeEnd <= rangeStart) {
|
||||
continue;
|
||||
}
|
||||
const size_t relStart = rangeStart - start;
|
||||
const size_t length = rangeEnd - rangeStart;
|
||||
if (relStart > static_cast<size_t>(std::numeric_limits<int>::max())
|
||||
|| length > static_cast<size_t>(std::numeric_limits<int>::max())) {
|
||||
continue;
|
||||
}
|
||||
setFormat(static_cast<int>(relStart), static_cast<int>(length), format[it->type]);
|
||||
}
|
||||
#else
|
||||
if (!code) {
|
||||
return;
|
||||
}
|
||||
auto block = currentBlock();
|
||||
size_t start = block.position();
|
||||
size_t end = block.position() + block.length();
|
||||
size_t start = static_cast<size_t>(block.position());
|
||||
size_t end = start + static_cast<size_t>(block.length());
|
||||
|
||||
auto cm = r_codemeta_in(code, start, end);
|
||||
if (cm == nullptr) {
|
||||
return;
|
||||
}
|
||||
auto processAnnotation = [&](RCodeMetaItem *annotation) {
|
||||
if (!annotation || annotation->type != R_CODEMETA_TYPE_SYNTAX_HIGHLIGHT) {
|
||||
return;
|
||||
}
|
||||
auto type = annotation->syntax_highlight.type;
|
||||
if (size_t(type) >= HIGHLIGHT_COUNT) {
|
||||
return;
|
||||
}
|
||||
auto range = intersectCodeMetaRange(annotation->start, annotation->end, start, end);
|
||||
if (!range) {
|
||||
return;
|
||||
}
|
||||
if (range->start > static_cast<size_t>(std::numeric_limits<int>::max())
|
||||
|| range->length > static_cast<size_t>(std::numeric_limits<int>::max())) {
|
||||
return;
|
||||
}
|
||||
setFormat(static_cast<int>(range->start), static_cast<int>(range->length), format[type]);
|
||||
};
|
||||
#if R2_ABIVERSION >= 40
|
||||
std::unique_ptr<RVecCodeMetaItemPtr, decltype(&RVecCodeMetaItemPtr_free)>
|
||||
annotations(cm, &RVecCodeMetaItemPtr_free);
|
||||
RCodeMetaItem **iter;
|
||||
R_VEC_FOREACH(annotations.get(), iter)
|
||||
{
|
||||
processAnnotation(*iter);
|
||||
}
|
||||
#else
|
||||
std::unique_ptr<RPVector, decltype(&r_pvector_free)> annotations(cm, &r_pvector_free);
|
||||
void **iter;
|
||||
r_pvector_foreach(annotations.get(), iter)
|
||||
{
|
||||
RCodeMetaItem *annotation = static_cast<RCodeMetaItem *>(*iter);
|
||||
if (annotation->type != R_CODEMETA_TYPE_SYNTAX_HIGHLIGHT) {
|
||||
continue;
|
||||
}
|
||||
auto type = annotation->syntax_highlight.type;
|
||||
if (size_t(type) >= HIGHLIGHT_COUNT) {
|
||||
continue;
|
||||
}
|
||||
auto annotationStart = annotation->start;
|
||||
if (annotationStart < start) {
|
||||
annotationStart = 0;
|
||||
} else {
|
||||
annotationStart -= start;
|
||||
}
|
||||
auto annotationEnd = annotation->end - start;
|
||||
|
||||
setFormat(annotationStart, annotationEnd - annotationStart, format[type]);
|
||||
processAnnotation(static_cast<RCodeMetaItem *>(*iter));
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
@@ -1,8 +1,16 @@
|
||||
#ifndef DECOMPILER_HIGHLIGHTER_H
|
||||
#define DECOMPILER_HIGHLIGHTER_H
|
||||
|
||||
// Define to cache and pre-sort syntax-highlight items once per setAnnotations()
|
||||
// instead of calling r_codemeta_in() per text block. Disabled by default; the
|
||||
// r_codemeta API is expected to be fast enough on its own.
|
||||
// #define IAITO_DECOMPILER_CACHE_HIGHLIGHTS 1
|
||||
|
||||
#include "IaitoCommon.h"
|
||||
#include <array>
|
||||
#ifdef IAITO_DECOMPILER_CACHE_HIGHLIGHTS
|
||||
#include <vector>
|
||||
#endif
|
||||
#include <QSyntaxHighlighter>
|
||||
#include <QTextCharFormat>
|
||||
#include <QTextDocument>
|
||||
@@ -38,6 +46,21 @@ private:
|
||||
static const int HIGHLIGHT_COUNT = R_SYNTAX_HIGHLIGHT_TYPE_GLOBAL_VARIABLE + 1;
|
||||
std::array<QTextCharFormat, HIGHLIGHT_COUNT> format;
|
||||
RCodeMeta *code = nullptr;
|
||||
|
||||
#ifdef IAITO_DECOMPILER_CACHE_HIGHLIGHTS
|
||||
struct HighlightItem
|
||||
{
|
||||
size_t start;
|
||||
size_t end;
|
||||
int type;
|
||||
};
|
||||
// Cached, start-sorted syntax-highlight items pulled from `code`.
|
||||
// Avoids calling r_codemeta_in() once per text block, whose tree walk
|
||||
// can degrade to O(annotations) per call and hang large documents.
|
||||
std::vector<HighlightItem> highlightItems;
|
||||
|
||||
void rebuildHighlightItems();
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,347 @@
|
||||
#include "DeepLink.h"
|
||||
|
||||
#include "MemoryDockWidget.h"
|
||||
#include "common/InitialOptions.h"
|
||||
#include "common/SamplesDB.h"
|
||||
#include "core/Iaito.h"
|
||||
#include "core/MainWindow.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QApplication>
|
||||
#include <QClipboard>
|
||||
#include <QFileInfo>
|
||||
#include <QInputDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QRegularExpression>
|
||||
#include <QUrl>
|
||||
#include <QUrlQuery>
|
||||
|
||||
namespace {
|
||||
|
||||
// Parsed query parameters shared by every handler.
|
||||
struct Params
|
||||
{
|
||||
QString addr;
|
||||
QString view;
|
||||
QString size;
|
||||
QString arch;
|
||||
QString cpu;
|
||||
QString os;
|
||||
QString bits;
|
||||
QString endian;
|
||||
QString base;
|
||||
};
|
||||
|
||||
// Strips the single leading slash QUrl keeps in the path of authority URIs.
|
||||
QString stripLeadingSlash(const QString &path)
|
||||
{
|
||||
return path.startsWith(QLatin1Char('/')) ? path.mid(1) : path;
|
||||
}
|
||||
|
||||
void applyAnalParams(InitialOptions &options, const Params &p)
|
||||
{
|
||||
if (!p.arch.isEmpty()) {
|
||||
options.arch = p.arch;
|
||||
}
|
||||
if (!p.cpu.isEmpty()) {
|
||||
options.cpu = p.cpu;
|
||||
}
|
||||
if (!p.os.isEmpty()) {
|
||||
options.os = p.os;
|
||||
}
|
||||
if (!p.bits.isEmpty()) {
|
||||
options.bits = p.bits.toInt();
|
||||
}
|
||||
if (p.endian.compare(QStringLiteral("big"), Qt::CaseInsensitive) == 0) {
|
||||
options.endian = InitialOptions::Endianness::Big;
|
||||
} else if (p.endian.compare(QStringLiteral("little"), Qt::CaseInsensitive) == 0) {
|
||||
options.endian = InitialOptions::Endianness::Little;
|
||||
}
|
||||
if (!p.base.isEmpty()) {
|
||||
options.binLoadAddr = Core()->math(p.base);
|
||||
options.mapAddr = options.binLoadAddr;
|
||||
}
|
||||
}
|
||||
|
||||
void showView(MainWindow *main, const QString &view)
|
||||
{
|
||||
const QString v = view.toLower();
|
||||
if (v == QStringLiteral("disasm")) {
|
||||
main->showMemoryWidget(MemoryWidgetType::Disassembly);
|
||||
} else if (v == QStringLiteral("graph")) {
|
||||
main->showMemoryWidget(MemoryWidgetType::Graph);
|
||||
} else if (v == QStringLiteral("hex")) {
|
||||
main->showMemoryWidget(MemoryWidgetType::Hexdump);
|
||||
} else if (v == QStringLiteral("decompiler")) {
|
||||
main->showMemoryWidget(MemoryWidgetType::Decompiler);
|
||||
} else {
|
||||
// strings, imports, exports, functions and any other dock panel
|
||||
main->focusPanelByName(v);
|
||||
}
|
||||
}
|
||||
|
||||
void applyLocation(MainWindow *main, const Params &p)
|
||||
{
|
||||
if (!p.size.isEmpty()) {
|
||||
// Resolve numerically so size= can never inject extra r2 commands.
|
||||
Core()->cmdRaw(QStringLiteral("b %1").arg(Core()->math(p.size)));
|
||||
}
|
||||
if (!p.addr.isEmpty()) {
|
||||
Core()->seek(p.addr);
|
||||
}
|
||||
if (!p.view.isEmpty()) {
|
||||
showView(main, p.view);
|
||||
} else if (!p.addr.isEmpty()) {
|
||||
Core()->seekAndShow(Core()->getOffset());
|
||||
}
|
||||
}
|
||||
|
||||
// Arms a one-shot uiReady handler that applies addr/view/size once the open
|
||||
// that follows finishes loading. Call this only right before an open is issued,
|
||||
// so a parse/lookup failure never leaves a connection that misfires on a later
|
||||
// unrelated open.
|
||||
void scheduleApply(MainWindow *main, const Params &p)
|
||||
{
|
||||
if (p.addr.isEmpty() && p.view.isEmpty() && p.size.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
auto conn = std::make_shared<QMetaObject::Connection>();
|
||||
*conn = QObject::connect(main, &MainWindow::uiReady, main, [main, p, conn]() {
|
||||
QObject::disconnect(*conn);
|
||||
applyLocation(main, p);
|
||||
});
|
||||
}
|
||||
|
||||
void openFile(MainWindow *main, const QString &path, const Params &p)
|
||||
{
|
||||
InitialOptions options;
|
||||
options.filename = path;
|
||||
applyAnalParams(options, p);
|
||||
scheduleApply(main, p);
|
||||
main->openNewFile(options, true);
|
||||
}
|
||||
|
||||
void openProject(MainWindow *main, const QString &name, const Params &p)
|
||||
{
|
||||
scheduleApply(main, p);
|
||||
main->openProject(name);
|
||||
}
|
||||
|
||||
void openBytes(MainWindow *main, const QString &hex, const Params &p)
|
||||
{
|
||||
QString clean = hex;
|
||||
clean.remove(QRegularExpression(QStringLiteral("[^0-9a-fA-F]")));
|
||||
if (clean.size() < 2) {
|
||||
QMessageBox::critical(
|
||||
main, QObject::tr("Deep link"), QObject::tr("Empty or invalid bytes payload."));
|
||||
return;
|
||||
}
|
||||
const int nbytes = clean.size() / 2;
|
||||
InitialOptions options;
|
||||
options.filename = QStringLiteral("malloc://%1").arg(nbytes);
|
||||
options.shellcode = clean.left(nbytes * 2);
|
||||
applyAnalParams(options, p);
|
||||
scheduleApply(main, p);
|
||||
main->openNewFile(options, true);
|
||||
}
|
||||
|
||||
// Returns true when host/path already refer to the file open in this session,
|
||||
// so a deep link to "another part of the same program" navigates in place
|
||||
// instead of reopening the binary.
|
||||
bool sameAsCurrent(const QString &host, const QString &path)
|
||||
{
|
||||
const QString current = Core()->getFilePath();
|
||||
if (current.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
const QString currentAbs = QFileInfo(current).absoluteFilePath();
|
||||
if (host.isEmpty() || host == QStringLiteral("file")) {
|
||||
return QFileInfo(path).absoluteFilePath() == currentAbs;
|
||||
}
|
||||
if (host == QStringLiteral("project")) {
|
||||
return stripLeadingSlash(path) == Core()->getConfig("prj.name");
|
||||
}
|
||||
if (host == QStringLiteral("sha256")) {
|
||||
const QString p = SamplesDB::pathForHash(stripLeadingSlash(path));
|
||||
return !p.isEmpty() && QFileInfo(p).absoluteFilePath() == currentAbs;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Looks up the sample by sha256 in the local database and opens it. Downloading
|
||||
// unknown samples is intentionally out of scope (see DEEPLINK.md); samples are
|
||||
// registered automatically when opened and via the New File dialog's Sha256 tab.
|
||||
void openHash(MainWindow *main, const QString &hash, const Params &p)
|
||||
{
|
||||
const QString path = SamplesDB::pathForHash(hash);
|
||||
if (!path.isEmpty() && QFileInfo::exists(path)) {
|
||||
openFile(main, path, p);
|
||||
return;
|
||||
}
|
||||
QMessageBox::warning(
|
||||
main,
|
||||
QObject::tr("Deep link"),
|
||||
QObject::tr(
|
||||
"No local sample is registered for SHA256:\n%1\n\nOpen the file once or use "
|
||||
"the Sha256 tab in the New File dialog to register it (database: %2).")
|
||||
.arg(hash.toLower(), SamplesDB::dbRoot()));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace DeepLink {
|
||||
|
||||
bool isDeepLink(const QString &arg)
|
||||
{
|
||||
return arg.startsWith(QStringLiteral("iaito://"), Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
bool handle(MainWindow *main, const QString &uri)
|
||||
{
|
||||
if (main == nullptr || !isDeepLink(uri)) {
|
||||
return false;
|
||||
}
|
||||
const QUrl url(uri, QUrl::TolerantMode);
|
||||
if (url.scheme().compare(QStringLiteral("iaito"), Qt::CaseInsensitive) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Params p;
|
||||
const QUrlQuery q(url);
|
||||
p.addr = q.queryItemValue(QStringLiteral("addr"));
|
||||
p.view = q.queryItemValue(QStringLiteral("view"));
|
||||
p.size = q.queryItemValue(QStringLiteral("size"));
|
||||
p.arch = q.queryItemValue(QStringLiteral("arch"));
|
||||
p.cpu = q.queryItemValue(QStringLiteral("cpu"));
|
||||
p.os = q.queryItemValue(QStringLiteral("os"));
|
||||
p.bits = q.queryItemValue(QStringLiteral("bits"));
|
||||
p.endian = q.queryItemValue(QStringLiteral("endian"));
|
||||
p.base = q.queryItemValue(QStringLiteral("base"));
|
||||
|
||||
const QString host = url.host().toLower();
|
||||
const QString path = url.path();
|
||||
|
||||
// A link into the already-open file just navigates, never reopens it.
|
||||
if (host != QStringLiteral("bytes") && sameAsCurrent(host, path)) {
|
||||
applyLocation(main, p);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (host.isEmpty() || host == QStringLiteral("file")) {
|
||||
// iaito:///bin/ls and iaito://file/bin/ls both map to /bin/ls
|
||||
if (path.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
openFile(main, path, p);
|
||||
} else if (host == QStringLiteral("project")) {
|
||||
openProject(main, stripLeadingSlash(path), p);
|
||||
} else if (host == QStringLiteral("sha256")) {
|
||||
openHash(main, stripLeadingSlash(path), p);
|
||||
} else if (host == QStringLiteral("bytes")) {
|
||||
openBytes(main, stripLeadingSlash(path), p);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
QString buildForOffset(QWidget *parent, RVA offset, const QString &view)
|
||||
{
|
||||
const QString filePath = Core()->getFilePath();
|
||||
const QString project = Core()->getConfig("prj.name");
|
||||
|
||||
// Reference candidates in priority order: project, sha256, absolute path.
|
||||
enum Kind { Project, Sha256, FilePath };
|
||||
QStringList labels;
|
||||
QList<int> kinds;
|
||||
if (!project.isEmpty()) {
|
||||
labels << QObject::tr("Project: %1").arg(project);
|
||||
kinds << Project;
|
||||
}
|
||||
if (!filePath.isEmpty() && QFileInfo::exists(filePath)) {
|
||||
labels << QObject::tr("SHA256 hash of the file");
|
||||
kinds << Sha256;
|
||||
labels << QObject::tr("File path: %1").arg(filePath);
|
||||
kinds << FilePath;
|
||||
}
|
||||
if (labels.isEmpty()) {
|
||||
QMessageBox::warning(
|
||||
parent,
|
||||
QObject::tr("Copy deep link"),
|
||||
QObject::tr("No file or project is open to reference."));
|
||||
return QString();
|
||||
}
|
||||
|
||||
bool ok = false;
|
||||
const QString choice = QInputDialog::getItem(
|
||||
parent,
|
||||
QObject::tr("Copy deep link"),
|
||||
QObject::tr("Reference the current file by:"),
|
||||
labels,
|
||||
0,
|
||||
false,
|
||||
&ok);
|
||||
if (!ok || choice.isEmpty()) {
|
||||
return QString();
|
||||
}
|
||||
const int kind = kinds.at(labels.indexOf(choice));
|
||||
|
||||
QUrl url;
|
||||
url.setScheme(QStringLiteral("iaito"));
|
||||
if (kind == Project) {
|
||||
url.setHost(QStringLiteral("project"));
|
||||
url.setPath(QStringLiteral("/") + project);
|
||||
} else if (kind == Sha256) {
|
||||
const QString hash = SamplesDB::registerFile(filePath);
|
||||
if (hash.isEmpty()) {
|
||||
QMessageBox::warning(
|
||||
parent,
|
||||
QObject::tr("Copy deep link"),
|
||||
QObject::tr("Could not compute the sha256 of:\n%1").arg(filePath));
|
||||
return QString();
|
||||
}
|
||||
url.setHost(QStringLiteral("sha256"));
|
||||
url.setPath(QStringLiteral("/") + hash);
|
||||
} else {
|
||||
url.setHost(QStringLiteral("file"));
|
||||
url.setPath(QFileInfo(filePath).absoluteFilePath());
|
||||
}
|
||||
|
||||
QUrlQuery q;
|
||||
if (offset != RVA_INVALID) {
|
||||
q.addQueryItem(QStringLiteral("addr"), RAddressString(offset));
|
||||
}
|
||||
if (!view.isEmpty()) {
|
||||
q.addQueryItem(QStringLiteral("view"), view);
|
||||
}
|
||||
url.setQuery(q);
|
||||
return url.toString(QUrl::FullyEncoded);
|
||||
}
|
||||
|
||||
void copyForOffset(QWidget *parent, RVA offset, const QString &view)
|
||||
{
|
||||
const QString link = buildForOffset(parent, offset, view);
|
||||
if (!link.isEmpty()) {
|
||||
QApplication::clipboard()->setText(link);
|
||||
}
|
||||
}
|
||||
|
||||
QString linkAt(const QString &line, int posInLine)
|
||||
{
|
||||
if (posInLine < 0 || posInLine > line.length()) {
|
||||
return QString();
|
||||
}
|
||||
// A deep link ends at whitespace or characters that cannot belong to a URI.
|
||||
static const QRegularExpression re(QStringLiteral("iaito://[^\\s\"'<>)\\]}]+"));
|
||||
auto it = re.globalMatch(line);
|
||||
while (it.hasNext()) {
|
||||
const QRegularExpressionMatch m = it.next();
|
||||
if (posInLine >= m.capturedStart() && posInLine < m.capturedEnd()) {
|
||||
return m.captured();
|
||||
}
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
} // namespace DeepLink
|
||||
@@ -0,0 +1,37 @@
|
||||
#ifndef IAITO_DEEPLINK_H
|
||||
#define IAITO_DEEPLINK_H
|
||||
|
||||
#include "core/IaitoCommon.h"
|
||||
|
||||
#include <QString>
|
||||
|
||||
class MainWindow;
|
||||
class QWidget;
|
||||
|
||||
// iaito:// deep link handling. See DEEPLINK.md for the URI specification.
|
||||
namespace DeepLink {
|
||||
|
||||
// Returns true if arg looks like an iaito:// deep link.
|
||||
bool isDeepLink(const QString &arg);
|
||||
|
||||
// Parses uri and opens the referenced file/project/sha256/bytes, applying the
|
||||
// addr/view/size query parameters once the UI is ready. Returns false when the
|
||||
// uri is not a valid iaito:// deep link.
|
||||
bool handle(MainWindow *main, const QString &uri);
|
||||
|
||||
// Builds an iaito:// deep link pointing at offset (and optionally a view) in the
|
||||
// currently open file. Prompts the user to choose how the file is referenced
|
||||
// (loaded project, sha256 or absolute path). Returns an empty string when the
|
||||
// user cancels or no reference is available.
|
||||
QString buildForOffset(QWidget *parent, RVA offset, const QString &view = QString());
|
||||
|
||||
// buildForOffset + copy the result to the clipboard. No-op when cancelled.
|
||||
void copyForOffset(QWidget *parent, RVA offset, const QString &view = QString());
|
||||
|
||||
// If posInLine falls inside an iaito:// link contained in line, returns that
|
||||
// link, otherwise an empty string. Used to make links in comments clickable.
|
||||
QString linkAt(const QString &line, int posInLine);
|
||||
|
||||
} // namespace DeepLink
|
||||
|
||||
#endif // IAITO_DEEPLINK_H
|
||||
@@ -0,0 +1,124 @@
|
||||
#include "ExceptionInfo.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <QCoreApplication>
|
||||
#include <QJsonObject>
|
||||
#include <QVariant>
|
||||
|
||||
static QString exceptionTr(const char *text)
|
||||
{
|
||||
return QCoreApplication::translate("ExceptionInfo", text);
|
||||
}
|
||||
|
||||
static quint64 exceptionAddress(const QJsonValue &value)
|
||||
{
|
||||
bool ok = false;
|
||||
quint64 result = ExceptionRegion::Invalid;
|
||||
if (value.isString()) {
|
||||
result = value.toString().toULongLong(&ok, 0);
|
||||
} else if (
|
||||
value.isDouble() && value.toDouble() >= 0
|
||||
&& std::floor(value.toDouble()) == value.toDouble()) {
|
||||
result = value.toVariant().toULongLong(&ok);
|
||||
}
|
||||
return ok ? result : ExceptionRegion::Invalid;
|
||||
}
|
||||
|
||||
bool ExceptionRegion::protects(quint64 address, quint64 size) const
|
||||
{
|
||||
// Subtraction avoids overflowing a block's end near the address-space limit.
|
||||
return size && address < to && (address >= from || from - address < size);
|
||||
}
|
||||
|
||||
bool ExceptionRegion::touches(quint64 address, quint64 size) const
|
||||
{
|
||||
return protects(address, size)
|
||||
|| (handler != Invalid && handler >= address && handler - address < size);
|
||||
}
|
||||
|
||||
QString ExceptionRegion::label() const
|
||||
{
|
||||
if (kind == QStringLiteral("cleanup")) {
|
||||
return exceptionTr("Cleanup / finally");
|
||||
}
|
||||
const QString name = kind == QStringLiteral("filter") ? exceptionTr("Filter")
|
||||
: exceptionTr("Catch");
|
||||
return QStringLiteral("%1 (%2)").arg(
|
||||
name,
|
||||
catchAll ? exceptionTr("all exceptions")
|
||||
: type.isEmpty() ? exceptionTr("unknown type")
|
||||
: type);
|
||||
}
|
||||
|
||||
QString ExceptionRegion::description() const
|
||||
{
|
||||
QString text = exceptionTr("#%1 · %2\nProtected: [0x%3, 0x%4)\nHandler: 0x%5")
|
||||
.arg(index)
|
||||
.arg(label())
|
||||
.arg(from, 0, 16)
|
||||
.arg(to, 0, 16)
|
||||
.arg(handler, 0, 16);
|
||||
if (source != Invalid) {
|
||||
text += exceptionTr("\nMetadata source: 0x%1").arg(source, 0, 16);
|
||||
}
|
||||
if (filter && filter != Invalid) {
|
||||
text += exceptionTr("\nFilter value / address: 0x%1").arg(filter, 0, 16);
|
||||
}
|
||||
if (typeFilter) {
|
||||
text += exceptionTr("\nType filter: %1").arg(typeFilter);
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
QColor ExceptionRegion::color() const
|
||||
{
|
||||
// Stable across views and independent of metadata order; shared ranges share a color.
|
||||
const quint64 hash = (from >> 2) ^ (to >> 1) ^ from;
|
||||
return QColor::fromHsv(int(hash % 12) * 30, 145, 210);
|
||||
}
|
||||
|
||||
QList<ExceptionRegion> ExceptionRegion::parse(const QJsonArray &array)
|
||||
{
|
||||
QList<ExceptionRegion> result;
|
||||
for (int i = 0; i < array.size(); ++i) {
|
||||
const auto object = array.at(i).toObject();
|
||||
ExceptionRegion region;
|
||||
region.from = exceptionAddress(object.value(QStringLiteral("from")));
|
||||
region.to = exceptionAddress(object.value(QStringLiteral("to")));
|
||||
region.handler = exceptionAddress(object.value(QStringLiteral("handler")));
|
||||
if (region.from == Invalid || region.to == Invalid || region.from >= region.to
|
||||
|| region.handler == Invalid) {
|
||||
continue;
|
||||
}
|
||||
region.index = exceptionAddress(object.value(QStringLiteral("index")));
|
||||
if (region.index == Invalid) {
|
||||
region.index = quint64(i);
|
||||
}
|
||||
region.source = exceptionAddress(object.value(QStringLiteral("source")));
|
||||
region.filter = exceptionAddress(object.value(QStringLiteral("filter")));
|
||||
region.kind = object.value(QStringLiteral("kind")).toString();
|
||||
region.type = object.value(QStringLiteral("type")).toString();
|
||||
region.typeFilter = object.value(QStringLiteral("typeFilter")).toVariant().toLongLong();
|
||||
region.catchAll = object.value(QStringLiteral("catchAll")).toBool();
|
||||
result.append(region);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QList<ExceptionRegion> ExceptionRegion::forBlocks(
|
||||
const QList<ExceptionRegion> ®ions, const QJsonArray &blocks)
|
||||
{
|
||||
QList<ExceptionRegion> result;
|
||||
for (const auto ®ion : regions) {
|
||||
for (const auto &value : blocks) {
|
||||
const auto block = value.toObject();
|
||||
const auto address = exceptionAddress(block.value(QStringLiteral("addr")));
|
||||
const auto size = exceptionAddress(block.value(QStringLiteral("size")));
|
||||
if (address != Invalid && size != Invalid && region.touches(address, size)) {
|
||||
result.append(region);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#ifndef EXCEPTIONINFO_H
|
||||
#define EXCEPTIONINFO_H
|
||||
|
||||
#include <limits>
|
||||
#include <QColor>
|
||||
#include <QJsonArray>
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
|
||||
// Addresses in iwj use half-open ranges: [from, to).
|
||||
struct ExceptionRegion
|
||||
{
|
||||
static constexpr quint64 Invalid = std::numeric_limits<quint64>::max();
|
||||
quint64 index = 0;
|
||||
quint64 source = Invalid;
|
||||
quint64 from = Invalid;
|
||||
quint64 to = Invalid;
|
||||
quint64 handler = Invalid;
|
||||
quint64 filter = 0;
|
||||
QString kind;
|
||||
QString type;
|
||||
qint64 typeFilter = 0;
|
||||
bool catchAll = false;
|
||||
|
||||
bool contains(quint64 address) const { return from <= address && address < to; }
|
||||
bool protects(quint64 address, quint64 size) const;
|
||||
bool touches(quint64 address, quint64 size) const;
|
||||
QString label() const;
|
||||
QString description() const;
|
||||
QColor color() const;
|
||||
static QList<ExceptionRegion> parse(const QJsonArray &array);
|
||||
static QList<ExceptionRegion> forBlocks(
|
||||
const QList<ExceptionRegion> ®ions, const QJsonArray &blocks);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -4,10 +4,16 @@
|
||||
#include <cmath>
|
||||
#include <QAbstractButton>
|
||||
#include <QAbstractItemView>
|
||||
#include <QApplication>
|
||||
#include <QComboBox>
|
||||
#include <QCompleter>
|
||||
#include <QCryptographicHash>
|
||||
#include <QDir>
|
||||
#include <QDockWidget>
|
||||
#include <QFileInfo>
|
||||
#include <QFileSystemModel>
|
||||
#include <QFont>
|
||||
#include <QLineEdit>
|
||||
#include <QMenu>
|
||||
#include <QPlainTextEdit>
|
||||
#include <QString>
|
||||
@@ -133,6 +139,41 @@ void setCheckedWithoutSignals(QAbstractButton *button, bool checked)
|
||||
button->blockSignals(blocked);
|
||||
}
|
||||
|
||||
void bindFont(QWidget *view, bool smallFont, bool monospace)
|
||||
{
|
||||
auto apply = [view, smallFont, monospace]() {
|
||||
QFont f = smallFont ? Config()->getSmallFont() : Config()->getFont();
|
||||
if (monospace) {
|
||||
f.setStyleHint(QFont::Monospace);
|
||||
}
|
||||
view->setFont(f);
|
||||
};
|
||||
apply();
|
||||
QObject::connect(Config(), &Configuration::fontsUpdated, view, apply);
|
||||
}
|
||||
|
||||
// Monospace code font scaled to the size of the regular UI font
|
||||
QFont logFont()
|
||||
{
|
||||
QFont f = Config()->getBaseFont();
|
||||
const QFont ui = QApplication::font();
|
||||
if (ui.pixelSize() > 0) {
|
||||
f.setPixelSize(ui.pixelSize());
|
||||
} else if (ui.pointSizeF() > 0) {
|
||||
f.setPointSizeF(ui.pointSizeF());
|
||||
}
|
||||
f.setStyleHint(QFont::Monospace);
|
||||
return f;
|
||||
}
|
||||
|
||||
void bindLogFont(QWidget *view)
|
||||
{
|
||||
view->setFont(logFont());
|
||||
QObject::connect(Config(), &Configuration::fontsUpdated, view, [view]() {
|
||||
view->setFont(logFont());
|
||||
});
|
||||
}
|
||||
|
||||
SizePolicyMinMax forceWidth(QWidget *widget, int width)
|
||||
{
|
||||
SizePolicyMinMax r;
|
||||
@@ -199,8 +240,7 @@ int getMaxFullyDisplayedLines(QPlainTextEdit *plainTextEdit)
|
||||
|
||||
QByteArray applyColorToSvg(const QByteArray &data, QColor color)
|
||||
{
|
||||
static const QRegularExpression styleRegExp(
|
||||
"(?:style=\".*fill:(.*?);.*?\")|(?:fill=\"(.*?)\")");
|
||||
static const QRegularExpression styleRegExp("(?:style=\".*fill:(.*?);.*?\")|(?:fill=\"(.*?)\")");
|
||||
|
||||
QString replaceStr = QStringLiteral("#%1").arg(color.rgb() & 0xffffff, 6, 16, QLatin1Char('0'));
|
||||
int replaceStrLen = replaceStr.length();
|
||||
@@ -225,7 +265,9 @@ QByteArray applyColorToSvg(const QByteArray &data, QColor color)
|
||||
QByteArray applyColorToSvg(const QString &filename, QColor color)
|
||||
{
|
||||
QFile file(filename);
|
||||
file.open(QIODevice::ReadOnly);
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return applyColorToSvg(file.readAll(), color);
|
||||
}
|
||||
@@ -315,4 +357,23 @@ void emitColumnChanged(QAbstractItemModel *model, int column)
|
||||
}
|
||||
}
|
||||
|
||||
void attachFilePathCompleter(QLineEdit *edit)
|
||||
{
|
||||
if (!edit) {
|
||||
return;
|
||||
}
|
||||
auto *fsModel = new QFileSystemModel(edit);
|
||||
fsModel->setRootPath(QDir::rootPath());
|
||||
fsModel->setFilter(QDir::AllEntries | QDir::NoDotAndDotDot | QDir::Hidden);
|
||||
auto *completer = new QCompleter(fsModel, edit);
|
||||
completer->setCompletionMode(QCompleter::PopupCompletion);
|
||||
completer->setMaxVisibleItems(12);
|
||||
#ifdef Q_OS_WIN
|
||||
completer->setCaseSensitivity(Qt::CaseInsensitive);
|
||||
#else
|
||||
completer->setCaseSensitivity(Qt::CaseSensitive);
|
||||
#endif
|
||||
edit->setCompleter(completer);
|
||||
}
|
||||
|
||||
} // namespace qhelpers
|
||||
@@ -23,6 +23,7 @@ class QAction;
|
||||
class QMenu;
|
||||
class QPaintDevice;
|
||||
class QComboBox;
|
||||
class QLineEdit;
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
|
||||
#define IAITO_QT_SKIP_EMPTY_PARTS QString::SkipEmptyParts
|
||||
@@ -65,6 +66,10 @@ IAITO_EXPORT void setVerticalScrollMode(QAbstractItemView *tw);
|
||||
|
||||
IAITO_EXPORT void setCheckedWithoutSignals(QAbstractButton *button, bool checked);
|
||||
|
||||
IAITO_EXPORT void bindFont(QWidget *view, bool smallFont = true, bool monospace = false);
|
||||
IAITO_EXPORT QFont logFont();
|
||||
IAITO_EXPORT void bindLogFont(QWidget *view);
|
||||
|
||||
struct IAITO_EXPORT SizePolicyMinMax
|
||||
{
|
||||
QSizePolicy sizePolicy;
|
||||
@@ -104,6 +109,8 @@ IAITO_EXPORT void selectIndexByData(QComboBox *comboBox, QVariant data, int defa
|
||||
*/
|
||||
IAITO_EXPORT void emitColumnChanged(QAbstractItemModel *model, int column);
|
||||
|
||||
IAITO_EXPORT void attachFilePathCompleter(QLineEdit *edit);
|
||||
|
||||
} // namespace qhelpers
|
||||
|
||||
#endif // HELPERS_H
|
||||
@@ -41,24 +41,23 @@ Highlighter::Highlighter(QTextDocument *parent)
|
||||
void Highlighter::highlightBlock(const QString &text)
|
||||
{
|
||||
for (const HighlightingRule &rule : highlightingRules) {
|
||||
QRegularExpression expression(rule.pattern);
|
||||
int index = expression.match(text).capturedStart();
|
||||
while (index >= 0) {
|
||||
int length = expression.match(text).capturedLength();
|
||||
setFormat(index, length, rule.format);
|
||||
index = expression.match(text.mid(index + length)).capturedStart();
|
||||
QRegularExpressionMatchIterator it = rule.pattern.globalMatch(text);
|
||||
while (it.hasNext()) {
|
||||
const QRegularExpressionMatch m = it.next();
|
||||
setFormat(m.capturedStart(), m.capturedLength(), rule.format);
|
||||
}
|
||||
}
|
||||
setCurrentBlockState(0);
|
||||
|
||||
int startIndex = 0;
|
||||
if (previousBlockState() != 1)
|
||||
startIndex = QRegularExpression(commentStartRegularExpression).match(text).capturedStart();
|
||||
if (previousBlockState() != 1) {
|
||||
startIndex = commentStartRegularExpression.match(text).capturedStart();
|
||||
}
|
||||
|
||||
while (startIndex >= 0) {
|
||||
QRegularExpressionMatch commentEndMatch
|
||||
= QRegularExpression(commentEndRegularExpression).match(text.mid(startIndex));
|
||||
int endIndex = commentEndMatch.capturedStart();
|
||||
const QRegularExpressionMatch commentEndMatch
|
||||
= commentEndRegularExpression.match(text, startIndex);
|
||||
const int endIndex = commentEndMatch.capturedStart();
|
||||
int commentLength;
|
||||
if (endIndex == -1) {
|
||||
setCurrentBlockState(1);
|
||||
@@ -67,8 +66,7 @@ void Highlighter::highlightBlock(const QString &text)
|
||||
commentLength = endIndex - startIndex + commentEndMatch.capturedLength();
|
||||
}
|
||||
setFormat(startIndex, commentLength, multiLineCommentFormat);
|
||||
startIndex = QRegularExpression(commentStartRegularExpression)
|
||||
.match(text.mid(startIndex + commentLength))
|
||||
.capturedStart();
|
||||
startIndex
|
||||
= commentStartRegularExpression.match(text, startIndex + commentLength).capturedStart();
|
||||
}
|
||||
}
|
||||
@@ -51,14 +51,16 @@ bool IOModesController::prepareForWriting()
|
||||
QMessageBox msgBox;
|
||||
msgBox.setIcon(QMessageBox::Icon::Critical);
|
||||
msgBox.setWindowTitle(QObject::tr("Write error"));
|
||||
msgBox.setText(QObject::tr("Your file is opened in read-only mode. "
|
||||
"Editing is only available when the file is "
|
||||
"opened in either Write or Cache modes.\n\n"
|
||||
"WARNING: In Write mode, any changes will be "
|
||||
"committed to the file on disk. "
|
||||
"For safety, please consider using Cache mode "
|
||||
"and then commit the changes manually "
|
||||
"via File -> Commit modifications to disk."));
|
||||
msgBox.setText(
|
||||
QObject::tr(
|
||||
"Your file is opened in read-only mode. "
|
||||
"Editing is only available when the file is "
|
||||
"opened in either Write or Cache modes.\n\n"
|
||||
"WARNING: In Write mode, any changes will be "
|
||||
"committed to the file on disk. "
|
||||
"For safety, please consider using Cache mode "
|
||||
"and then commit the changes manually "
|
||||
"via File -> Commit modifications to disk."));
|
||||
msgBox.addButton(QObject::tr("Cancel"), QMessageBox::RejectRole);
|
||||
QAbstractButton *reopenButton
|
||||
= msgBox.addButton(QObject::tr("Reopen in Write mode"), QMessageBox::YesRole);
|
||||
@@ -100,11 +102,12 @@ bool IOModesController::askCommitUnsavedChanges()
|
||||
QMessageBox::StandardButton ret = QMessageBox::question(
|
||||
NULL,
|
||||
QObject::tr("Uncomitted changes"),
|
||||
QObject::tr("It seems that you have changes or patches that are "
|
||||
"not committed to the file.\n"
|
||||
"Do you want to commit them now?"),
|
||||
(QMessageBox::StandardButtons)(
|
||||
QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel));
|
||||
QObject::tr(
|
||||
"It seems that you have changes or patches that are "
|
||||
"not committed to the file.\n"
|
||||
"Do you want to commit them now?"),
|
||||
(QMessageBox::StandardButtons) (QMessageBox::Save | QMessageBox::Discard
|
||||
| QMessageBox::Cancel));
|
||||
if (ret == QMessageBox::Save) {
|
||||
Core()->commitWriteCache();
|
||||
} else if (ret == QMessageBox::Discard) {
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
#include "IaitoSeekable.h"
|
||||
#include "core/MainWindow.h"
|
||||
|
||||
#include <QPlainTextEdit>
|
||||
|
||||
IaitoSeekable::IaitoSeekable(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
widgetOffset = Core()->getOffset();
|
||||
connect(Core(), &IaitoCore::seekChanged, this, &IaitoSeekable::onCoreSeekChanged);
|
||||
}
|
||||
|
||||
@@ -13,35 +11,98 @@ IaitoSeekable::~IaitoSeekable() {}
|
||||
|
||||
void IaitoSeekable::setSynchronization(bool sync)
|
||||
{
|
||||
if (synchronized == sync) {
|
||||
return;
|
||||
}
|
||||
const RVA address = Core()->getOffset();
|
||||
synchronized = sync;
|
||||
onCoreSeekChanged(Core()->getOffset());
|
||||
seekHistory = {address};
|
||||
historyIndex = 0;
|
||||
updateSeek(address);
|
||||
emit syncChanged();
|
||||
emit historyChanged();
|
||||
}
|
||||
|
||||
void IaitoSeekable::onCoreSeekChanged(RVA addr)
|
||||
{
|
||||
if (synchronized && widgetOffset != addr) {
|
||||
updateSeek(addr, true);
|
||||
if (synchronized) {
|
||||
updateSeek(addr);
|
||||
emit historyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void IaitoSeekable::updateSeek(RVA addr, bool localOnly)
|
||||
void IaitoSeekable::seek(RVA addr)
|
||||
{
|
||||
previousOffset = widgetOffset;
|
||||
widgetOffset = addr;
|
||||
if (synchronized && !localOnly) {
|
||||
Core()->seek(addr);
|
||||
if (addr == RVA_INVALID) {
|
||||
return;
|
||||
}
|
||||
if (synchronized) {
|
||||
Core()->seek(addr);
|
||||
return;
|
||||
}
|
||||
if (addr == widgetOffset) {
|
||||
return;
|
||||
}
|
||||
seekHistory.resize(historyIndex + 1);
|
||||
seekHistory.append(addr);
|
||||
++historyIndex;
|
||||
updateSeek(addr);
|
||||
emit historyChanged();
|
||||
}
|
||||
|
||||
void IaitoSeekable::updateSeek(RVA addr)
|
||||
{
|
||||
if (addr == RVA_INVALID || addr == widgetOffset) {
|
||||
return;
|
||||
}
|
||||
widgetOffset = addr;
|
||||
emit seekableSeekChanged(addr);
|
||||
}
|
||||
|
||||
bool IaitoSeekable::canSeekPrev() const
|
||||
{
|
||||
if (synchronized) {
|
||||
return Core()->core()->io->undo.undos > 0;
|
||||
}
|
||||
return historyIndex > 0;
|
||||
}
|
||||
|
||||
bool IaitoSeekable::canSeekNext() const
|
||||
{
|
||||
if (synchronized) {
|
||||
return Core()->core()->io->undo.redos > 0;
|
||||
}
|
||||
return historyIndex + 1 < seekHistory.size();
|
||||
}
|
||||
|
||||
QVector<RVA> IaitoSeekable::localHistory(bool forward) const
|
||||
{
|
||||
QVector<RVA> result;
|
||||
if (!synchronized) {
|
||||
for (int i = historyIndex; i >= 0 && i < seekHistory.size(); i += forward ? 1 : -1) {
|
||||
result.append(seekHistory.at(i));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void IaitoSeekable::seekPrev()
|
||||
{
|
||||
if (synchronized) {
|
||||
Core()->seekPrev();
|
||||
} else {
|
||||
this->seek(previousOffset);
|
||||
} else if (canSeekPrev()) {
|
||||
updateSeek(seekHistory.at(--historyIndex));
|
||||
emit historyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void IaitoSeekable::seekNext()
|
||||
{
|
||||
if (synchronized) {
|
||||
Core()->seekNext();
|
||||
} else if (canSeekNext()) {
|
||||
updateSeek(seekHistory.at(++historyIndex));
|
||||
emit historyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/Iaito.h"
|
||||
#include <QVector>
|
||||
|
||||
class MainWindow;
|
||||
|
||||
@@ -20,7 +21,7 @@ public:
|
||||
* In any case, IaitoSeekable::seekableSeekChanged is emitted.
|
||||
* @param addr the location to seek at.
|
||||
*/
|
||||
void seek(RVA addr) { updateSeek(addr, false); }
|
||||
void seek(RVA addr);
|
||||
|
||||
/**
|
||||
* @brief setSynchronization sets
|
||||
@@ -44,6 +45,10 @@ public:
|
||||
*/
|
||||
bool isSynchronized();
|
||||
|
||||
bool canSeekPrev() const;
|
||||
bool canSeekNext() const;
|
||||
QVector<RVA> localHistory(bool forward) const;
|
||||
|
||||
/**
|
||||
* @brief seekToReference will seek to the function or the object which is
|
||||
* referenced in a given offset
|
||||
@@ -56,6 +61,7 @@ public slots:
|
||||
* @brief seekPrev seeks to last location.
|
||||
*/
|
||||
void seekPrev();
|
||||
void seekNext();
|
||||
|
||||
/**
|
||||
* @brief toggleSyncWithCore toggles
|
||||
@@ -75,11 +81,8 @@ private:
|
||||
*/
|
||||
RVA widgetOffset = RVA_INVALID;
|
||||
|
||||
/**
|
||||
* @brief previousOffset last seek location.
|
||||
* @todo maybe use an actual history?
|
||||
*/
|
||||
RVA previousOffset = RVA_INVALID;
|
||||
QVector<RVA> seekHistory;
|
||||
int historyIndex = 0;
|
||||
|
||||
/**
|
||||
* @brief synchronized tells with the seekable's offset is
|
||||
@@ -89,12 +92,11 @@ private:
|
||||
|
||||
/**
|
||||
* @brief internal method for changing the seek
|
||||
* @param localOnly whether the seek should be updated globally if
|
||||
* synchronized
|
||||
*/
|
||||
void updateSeek(RVA addr, bool localOnly);
|
||||
void updateSeek(RVA addr);
|
||||
|
||||
signals:
|
||||
void seekableSeekChanged(RVA addr);
|
||||
void syncChanged();
|
||||
void historyChanged();
|
||||
};
|
||||
@@ -30,11 +30,18 @@ struct InitialOptions
|
||||
QString os;
|
||||
bool analVars = true;
|
||||
|
||||
Endianness endian;
|
||||
Endianness endian = Endianness::Auto;
|
||||
|
||||
bool writeEnabled = false;
|
||||
bool loadBinInfo = true;
|
||||
bool loadBinCache = false;
|
||||
/**
|
||||
* @brief When true, iaito is opening the target as a live debug session.
|
||||
* setupAndStartAnalysis() will set cfg.debug=true before loadFile so the
|
||||
* rest of the pipeline (Dashboard, MainWindow, widgets, etc.) treats the
|
||||
* session as a debug one even before currentlyDebugging has been set.
|
||||
*/
|
||||
bool debug = false;
|
||||
QString forceBinPlugin;
|
||||
|
||||
bool demangle = true;
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
#include "common/Markdown.h"
|
||||
|
||||
#include <QScrollBar>
|
||||
#include <QTextBrowser>
|
||||
#include <QTextDocument>
|
||||
#include <QTextOption>
|
||||
#include <QtGlobal>
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
||||
#define IAITO_HAS_NATIVE_MARKDOWN_READER QT_CONFIG(textmarkdownreader)
|
||||
#else
|
||||
#define IAITO_HAS_NATIVE_MARKDOWN_READER 0
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
QString markdownStyleSheet()
|
||||
{
|
||||
return QStringLiteral(
|
||||
"h1 { margin-top: 0.45em; margin-bottom: 0.35em; font-size: 160%; }"
|
||||
"h2 { margin-top: 0.45em; margin-bottom: 0.30em; font-size: 140%; }"
|
||||
"h3 { margin-top: 0.40em; margin-bottom: 0.25em; font-size: 125%; }"
|
||||
"h4, h5, h6 { margin-top: 0.35em; margin-bottom: 0.20em; }"
|
||||
"p { margin-top: 0; margin-bottom: 0.65em; }"
|
||||
"ul, ol { margin-top: 0.20em; margin-bottom: 0.65em; }"
|
||||
"li { margin-top: 0.12em; margin-bottom: 0.12em; }"
|
||||
"blockquote { margin-left: 1em; padding-left: 0.6em; }"
|
||||
"pre { margin-top: 0.25em; margin-bottom: 0.75em; white-space: pre-wrap; }"
|
||||
"code { font-family: monospace; }"
|
||||
".task-checkbox { font-family: sans-serif; }");
|
||||
}
|
||||
|
||||
void setDocumentMarkdown(QTextDocument *document, const QString &markdown)
|
||||
{
|
||||
if (!document) {
|
||||
return;
|
||||
}
|
||||
|
||||
document->setDocumentMargin(8);
|
||||
#ifndef QT_NO_TEXTHTMLPARSER
|
||||
document->setDefaultStyleSheet(markdownStyleSheet());
|
||||
#endif
|
||||
|
||||
#if IAITO_HAS_NATIVE_MARKDOWN_READER
|
||||
document->setMarkdown(markdown, QTextDocument::MarkdownDialectGitHub);
|
||||
#else
|
||||
document->setPlainText(markdown);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace Markdown {
|
||||
|
||||
void configureBrowser(QTextBrowser *browser)
|
||||
{
|
||||
if (!browser) {
|
||||
return;
|
||||
}
|
||||
browser->setReadOnly(true);
|
||||
browser->setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
|
||||
browser->document()->setDocumentMargin(8);
|
||||
}
|
||||
|
||||
void render(QTextBrowser *browser, const QString &markdown)
|
||||
{
|
||||
if (!browser) {
|
||||
return;
|
||||
}
|
||||
|
||||
QScrollBar *bar = browser->verticalScrollBar();
|
||||
const bool wasAtBottom = !bar || bar->value() >= bar->maximum() - 4;
|
||||
setDocumentMarkdown(browser->document(), markdown);
|
||||
if (bar && wasAtBottom) {
|
||||
bar->setValue(bar->maximum());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Markdown
|
||||
|
||||
#undef IAITO_HAS_NATIVE_MARKDOWN_READER
|
||||
@@ -0,0 +1,15 @@
|
||||
#ifndef MARKDOWN_H
|
||||
#define MARKDOWN_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
class QTextBrowser;
|
||||
|
||||
namespace Markdown {
|
||||
|
||||
void configureBrowser(QTextBrowser *browser);
|
||||
void render(QTextBrowser *browser, const QString &markdown);
|
||||
|
||||
} // namespace Markdown
|
||||
|
||||
#endif // MARKDOWN_H
|
||||
@@ -30,12 +30,10 @@ MdHighlighter::MdHighlighter(QTextDocument *parent)
|
||||
void MdHighlighter::highlightBlock(const QString &text)
|
||||
{
|
||||
for (const HighlightingRule &rule : highlightingRules) {
|
||||
QRegularExpression expression(rule.pattern);
|
||||
int index = expression.match(text).capturedStart();
|
||||
while (index >= 0) {
|
||||
int length = expression.match(text).capturedLength();
|
||||
setFormat(index, length, rule.format);
|
||||
index = expression.match(text.mid(index + length)).capturedStart();
|
||||
QRegularExpressionMatchIterator it = rule.pattern.globalMatch(text);
|
||||
while (it.hasNext()) {
|
||||
const QRegularExpressionMatch m = it.next();
|
||||
setFormat(m.capturedStart(), m.capturedLength(), rule.format);
|
||||
}
|
||||
}
|
||||
setCurrentBlockState(0);
|
||||
|
||||
@@ -0,0 +1,436 @@
|
||||
#include "NameListModel.h"
|
||||
|
||||
#include <QStringList>
|
||||
|
||||
namespace {
|
||||
|
||||
QString compactName(const QString &name, const QString &previous)
|
||||
{
|
||||
int prefix = 0;
|
||||
const int limit = qMin(name.size(), previous.size());
|
||||
while (prefix < limit && name.at(prefix) == previous.at(prefix)) {
|
||||
++prefix;
|
||||
}
|
||||
if (prefix > 0 && prefix < name.size() && name.at(prefix).isLowSurrogate()) {
|
||||
--prefix;
|
||||
}
|
||||
return prefix > 2 && prefix < name.size() ? QStringLiteral("▶ ") + name.mid(prefix) : name;
|
||||
}
|
||||
|
||||
QStringList nameParts(const QString &name)
|
||||
{
|
||||
QStringList parts;
|
||||
int start = 0;
|
||||
int templates = 0;
|
||||
int parentheses = 0;
|
||||
int brackets = 0;
|
||||
for (int i = 0; i < name.size(); ++i) {
|
||||
const QChar c = name.at(i);
|
||||
// Operators and their signatures belong to the leaf, including operator< and operator().
|
||||
if (!templates && !parentheses && i == start && name.mid(i, 8) == QLatin1String("operator")
|
||||
&& (i + 8 == name.size()
|
||||
|| (!name.at(i + 8).isLetterOrNumber() && name.at(i + 8) != QLatin1Char('_')))) {
|
||||
break;
|
||||
}
|
||||
if (c == QLatin1Char('<')) {
|
||||
++templates;
|
||||
} else if (c == QLatin1Char('>') && templates) {
|
||||
--templates;
|
||||
} else if (c == QLatin1Char('(')) {
|
||||
++parentheses;
|
||||
} else if (c == QLatin1Char(')') && parentheses) {
|
||||
--parentheses;
|
||||
} else if (c == QLatin1Char('[')) {
|
||||
++brackets;
|
||||
} else if (c == QLatin1Char(']') && brackets) {
|
||||
--brackets;
|
||||
}
|
||||
if (templates || parentheses || brackets) {
|
||||
continue;
|
||||
}
|
||||
int separator = 0;
|
||||
if (c == QLatin1Char(':') && i + 1 < name.size() && name.at(i + 1) == c) {
|
||||
separator = 2;
|
||||
} else if (c == QLatin1Char('-') && i + 1 < name.size() && name.at(i + 1) == QLatin1Char('>')) {
|
||||
separator = 2;
|
||||
} else if (c == QLatin1Char('.') || c == QLatin1Char('/') || c == QLatin1Char('$')) {
|
||||
separator = 1;
|
||||
}
|
||||
if (separator) {
|
||||
i += separator - 1;
|
||||
if (i + 1 < name.size()) {
|
||||
parts.append(name.mid(start, i + 1 - start));
|
||||
start = i + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
parts.append(name.mid(start));
|
||||
return parts;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
NameListModel::NameListModel(AddressableItemModelI *source, int nameColumn, QObject *parent)
|
||||
: AddressableItemModel<QAbstractProxyModel>(parent)
|
||||
, nameColumn(nameColumn)
|
||||
{
|
||||
setSourceModel(source->asItemModel());
|
||||
}
|
||||
|
||||
NameListModel::~NameListModel() = default;
|
||||
|
||||
void NameListModel::setSourceModel(QAbstractItemModel *source)
|
||||
{
|
||||
beginRebuild();
|
||||
if (sourceModel()) {
|
||||
disconnect(sourceModel(), nullptr, this, nullptr);
|
||||
}
|
||||
QAbstractProxyModel::setSourceModel(source);
|
||||
sourceProvider = dynamic_cast<AddressableItemModelI *>(source);
|
||||
if (source) {
|
||||
if (auto filter = qobject_cast<AddressableFilterProxyModel *>(source)) {
|
||||
// QSortFilterProxyModel can remove thousands of disjoint row ranges for one query.
|
||||
// Rebuild the presentation once, after the entire filter operation has finished.
|
||||
connect(filter, &AddressableFilterProxyModel::filterAboutToChange, this, [this] {
|
||||
++filterChangeDepth;
|
||||
beginRebuild();
|
||||
});
|
||||
connect(filter, &AddressableFilterProxyModel::filterChanged, this, [this] {
|
||||
--filterChangeDepth;
|
||||
endRebuild();
|
||||
});
|
||||
}
|
||||
connect(source, &QAbstractItemModel::modelAboutToBeReset, this, &NameListModel::beginRebuild);
|
||||
connect(source, &QAbstractItemModel::modelReset, this, &NameListModel::endRebuild);
|
||||
connect(
|
||||
source, &QAbstractItemModel::layoutAboutToBeChanged, this, &NameListModel::beginRebuild);
|
||||
connect(source, &QAbstractItemModel::layoutChanged, this, &NameListModel::endRebuild);
|
||||
connect(
|
||||
source, &QAbstractItemModel::rowsAboutToBeInserted, this, &NameListModel::beginRebuild);
|
||||
connect(source, &QAbstractItemModel::rowsInserted, this, &NameListModel::endRebuild);
|
||||
connect(source, &QAbstractItemModel::rowsAboutToBeRemoved, this, &NameListModel::beginRebuild);
|
||||
connect(source, &QAbstractItemModel::rowsRemoved, this, &NameListModel::endRebuild);
|
||||
connect(source, &QAbstractItemModel::rowsAboutToBeMoved, this, &NameListModel::beginRebuild);
|
||||
connect(source, &QAbstractItemModel::rowsMoved, this, &NameListModel::endRebuild);
|
||||
connect(source, &QAbstractItemModel::dataChanged, this, &NameListModel::sourceDataChanged);
|
||||
connect(
|
||||
source, &QAbstractItemModel::headerDataChanged, this, &NameListModel::headerDataChanged);
|
||||
connect(source, &QObject::destroyed, this, [this] {
|
||||
sourceProvider = nullptr;
|
||||
beginRebuild();
|
||||
endRebuild();
|
||||
});
|
||||
}
|
||||
endRebuild();
|
||||
}
|
||||
|
||||
NameListModel::Node *NameListModel::node(const QModelIndex &index) const
|
||||
{
|
||||
return index.isValid() ? static_cast<Node *>(index.internalPointer()) : nullptr;
|
||||
}
|
||||
|
||||
QModelIndex NameListModel::nodeIndex(Node *item, int column) const
|
||||
{
|
||||
return item && item != &root ? createIndex(item->row, column, item) : QModelIndex();
|
||||
}
|
||||
|
||||
QModelIndex NameListModel::index(int row, int column, const QModelIndex &parent) const
|
||||
{
|
||||
if (row < 0 || column < 0 || column >= columnCount() || (parent.isValid() && parent.column())) {
|
||||
return {};
|
||||
}
|
||||
const Node *item = parent.isValid() ? node(parent) : &root;
|
||||
return row < int(item->children.size()) ? nodeIndex(item->children[row].get(), column)
|
||||
: QModelIndex();
|
||||
}
|
||||
|
||||
QModelIndex NameListModel::parent(const QModelIndex &index) const
|
||||
{
|
||||
auto item = node(index);
|
||||
return item ? nodeIndex(item->parent) : QModelIndex();
|
||||
}
|
||||
|
||||
int NameListModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
if (parent.isValid() && parent.column()) {
|
||||
return 0;
|
||||
}
|
||||
const Node *item = parent.isValid() ? node(parent) : &root;
|
||||
return int(item->children.size());
|
||||
}
|
||||
|
||||
int NameListModel::columnCount(const QModelIndex &) const
|
||||
{
|
||||
return sourceProvider ? sourceModel()->columnCount() : 0;
|
||||
}
|
||||
|
||||
bool NameListModel::hasChildren(const QModelIndex &parent) const
|
||||
{
|
||||
return rowCount(parent) > 0;
|
||||
}
|
||||
|
||||
QModelIndex NameListModel::mapToSource(const QModelIndex &index) const
|
||||
{
|
||||
auto item = node(index);
|
||||
return sourceProvider && item && item->source.isValid()
|
||||
? item->source.sibling(item->source.row(), index.column())
|
||||
: QModelIndex();
|
||||
}
|
||||
|
||||
QModelIndex NameListModel::mapFromSource(const QModelIndex &index) const
|
||||
{
|
||||
return index.isValid()
|
||||
? nodeIndex(sourceNodes.value(index.sibling(index.row(), 0)), index.column())
|
||||
: QModelIndex();
|
||||
}
|
||||
|
||||
QVariant NameListModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
auto item = node(index);
|
||||
if (!item) {
|
||||
return {};
|
||||
}
|
||||
if (!item->source.isValid()) {
|
||||
if (role == GroupPathRole) {
|
||||
return item->groupPath;
|
||||
}
|
||||
if (index.column() == nameColumn) {
|
||||
if (role == Qt::DisplayRole) {
|
||||
return item->label;
|
||||
}
|
||||
if (role == Qt::ToolTipRole) {
|
||||
return QStringLiteral("<qt>%1</qt>").arg(item->groupPath.toHtmlEscaped());
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
if (index.column() == nameColumn && item->named) {
|
||||
if (role == Qt::ToolTipRole) {
|
||||
const auto tooltip = mapToSource(index).data(NameToolTipRole);
|
||||
if (tooltip.isValid()) {
|
||||
return tooltip;
|
||||
}
|
||||
return QStringLiteral("<qt>%1</qt>").arg(item->fullName.toHtmlEscaped());
|
||||
}
|
||||
if (role == Qt::DisplayRole) {
|
||||
QString label = item->label;
|
||||
if (compact && item->row > 0) {
|
||||
auto previous = item->parent->children[item->row - 1].get();
|
||||
if (previous->named) {
|
||||
label = compactName(label, previous->label);
|
||||
}
|
||||
}
|
||||
const QString original = mapToSource(index).data().toString();
|
||||
// Keep decorations represented as text, such as a function's pin emoji.
|
||||
if (original.endsWith(item->fullName)) {
|
||||
return original.left(original.size() - item->fullName.size()) + label;
|
||||
}
|
||||
return label;
|
||||
}
|
||||
}
|
||||
return mapToSource(index).data(role);
|
||||
}
|
||||
|
||||
Qt::ItemFlags NameListModel::flags(const QModelIndex &index) const
|
||||
{
|
||||
if (!index.isValid()) {
|
||||
return Qt::NoItemFlags;
|
||||
}
|
||||
auto source = mapToSource(index);
|
||||
return source.isValid() ? source.flags() : Qt::ItemIsEnabled | Qt::ItemIsSelectable;
|
||||
}
|
||||
|
||||
QVariant NameListModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
return sourceProvider ? sourceModel()->headerData(section, orientation, role) : QVariant();
|
||||
}
|
||||
|
||||
void NameListModel::sort(int column, Qt::SortOrder order)
|
||||
{
|
||||
if (sourceProvider) {
|
||||
sourceModel()->sort(column, order);
|
||||
}
|
||||
}
|
||||
|
||||
RVA NameListModel::address(const QModelIndex &index) const
|
||||
{
|
||||
const auto source = mapToSource(index);
|
||||
return sourceProvider && source.isValid() ? sourceProvider->address(source) : RVA_INVALID;
|
||||
}
|
||||
|
||||
QString NameListModel::name(const QModelIndex &index) const
|
||||
{
|
||||
const auto source = mapToSource(index);
|
||||
return sourceProvider && source.isValid() ? sourceProvider->name(source) : QString();
|
||||
}
|
||||
|
||||
void NameListModel::beginRebuild()
|
||||
{
|
||||
if (!resetting) {
|
||||
resetting = true;
|
||||
beginResetModel();
|
||||
}
|
||||
}
|
||||
|
||||
void NameListModel::endRebuild()
|
||||
{
|
||||
if (resetting && !filterChangeDepth) {
|
||||
rebuild();
|
||||
resetting = false;
|
||||
endResetModel();
|
||||
}
|
||||
}
|
||||
|
||||
void NameListModel::appendSource(
|
||||
Node *parent, const QModelIndex &source, const QString &label, bool named)
|
||||
{
|
||||
auto item = std::make_unique<Node>();
|
||||
item->parent = parent;
|
||||
item->row = int(parent->children.size());
|
||||
item->source = source;
|
||||
item->label = label;
|
||||
item->named = named;
|
||||
if (named) {
|
||||
item->fullName = sourceProvider->name(source);
|
||||
}
|
||||
auto ptr = item.get();
|
||||
sourceNodes.insert(source, ptr);
|
||||
parent->children.push_back(std::move(item));
|
||||
// Preserve existing class/member relationships and unnamed function detail rows.
|
||||
if (sourceModel()->hasChildren(source)) {
|
||||
for (int row = 0; row < sourceModel()->rowCount(source); ++row) {
|
||||
auto child = sourceModel()->index(row, 0, source);
|
||||
QString childName = sourceProvider->name(child);
|
||||
const bool childNamed = !childName.isEmpty() && childName != ptr->fullName;
|
||||
if (childNamed) {
|
||||
for (const QString &separator :
|
||||
{QStringLiteral("::"),
|
||||
QStringLiteral("."),
|
||||
QStringLiteral("/"),
|
||||
QStringLiteral("$"),
|
||||
QStringLiteral("->")}) {
|
||||
const QString prefix = ptr->fullName + separator;
|
||||
if (childName.startsWith(prefix)) {
|
||||
childName.remove(0, prefix.size());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
appendSource(ptr, child, childName, childNamed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NameListModel::rebuild()
|
||||
{
|
||||
sourceNodes.clear();
|
||||
groups.clear();
|
||||
root.children.clear();
|
||||
if (!sourceProvider) {
|
||||
return;
|
||||
}
|
||||
for (int row = 0; row < sourceModel()->rowCount(); ++row) {
|
||||
auto source = sourceModel()->index(row, 0);
|
||||
const QString fullName = sourceProvider->name(source);
|
||||
Node *parent = &root;
|
||||
QString label = fullName;
|
||||
if (hierarchy) {
|
||||
const auto parts = nameParts(fullName);
|
||||
QString path;
|
||||
for (int part = 0; part + 1 < parts.size(); ++part) {
|
||||
path += parts[part];
|
||||
auto group = groups.value(path);
|
||||
if (!group) {
|
||||
auto item = std::make_unique<Node>();
|
||||
item->parent = parent;
|
||||
item->row = int(parent->children.size());
|
||||
item->label = parts[part];
|
||||
item->groupPath = path;
|
||||
group = item.get();
|
||||
parent->children.push_back(std::move(item));
|
||||
groups.insert(path, group);
|
||||
}
|
||||
parent = group;
|
||||
}
|
||||
label = parts.last();
|
||||
}
|
||||
appendSource(parent, source, label, true);
|
||||
}
|
||||
collapseSingleGroups(&root);
|
||||
}
|
||||
|
||||
void NameListModel::collapseSingleGroups(Node *parent)
|
||||
{
|
||||
for (auto &child : parent->children) {
|
||||
if (child->source.isValid()) {
|
||||
continue; // Class/member relationships belong to the source model.
|
||||
}
|
||||
collapseSingleGroups(child.get());
|
||||
if (child->children.size() == 1 && child->children.front()->source.isValid()) {
|
||||
auto group = std::move(child);
|
||||
child = std::move(group->children.front());
|
||||
child->label.prepend(group->label);
|
||||
child->parent = parent;
|
||||
child->row = group->row;
|
||||
groups.remove(group->groupPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NameListModel::sourceDataChanged(const QModelIndex &first, const QModelIndex &last)
|
||||
{
|
||||
if (resetting) {
|
||||
return;
|
||||
}
|
||||
for (int row = first.row(); row <= last.row(); ++row) {
|
||||
auto source = sourceModel()->index(row, 0, first.parent());
|
||||
auto item = sourceNodes.value(source);
|
||||
if (item && item->named && item->fullName != sourceProvider->name(source)) {
|
||||
beginRebuild();
|
||||
endRebuild();
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (int row = first.row(); row <= last.row(); ++row) {
|
||||
auto source = sourceModel()->index(row, 0, first.parent());
|
||||
auto item = sourceNodes.value(source);
|
||||
if (item) {
|
||||
emit dataChanged(nodeIndex(item, first.column()), nodeIndex(item, last.column()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NameListModel::namesChanged(Node *parent)
|
||||
{
|
||||
if (!parent->children.empty()) {
|
||||
emit dataChanged(
|
||||
nodeIndex(parent->children.front().get(), nameColumn),
|
||||
nodeIndex(parent->children.back().get(), nameColumn),
|
||||
{Qt::DisplayRole});
|
||||
for (const auto &child : parent->children) {
|
||||
namesChanged(child.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NameListModel::setCompact(bool enabled)
|
||||
{
|
||||
if (compact != enabled) {
|
||||
compact = enabled;
|
||||
namesChanged(&root);
|
||||
}
|
||||
}
|
||||
|
||||
void NameListModel::setHierarchy(bool enabled)
|
||||
{
|
||||
if (hierarchy != enabled) {
|
||||
beginRebuild();
|
||||
hierarchy = enabled;
|
||||
endRebuild();
|
||||
}
|
||||
}
|
||||
|
||||
QModelIndex NameListModel::groupIndex(const QString &path) const
|
||||
{
|
||||
return nodeIndex(groups.value(path));
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
#ifndef NAMELISTMODEL_H
|
||||
#define NAMELISTMODEL_H
|
||||
|
||||
#include "AddressableItemModel.h"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <QAbstractProxyModel>
|
||||
#include <QHash>
|
||||
|
||||
// Presentation only: all addresses, descriptions and edits belong to the source model.
|
||||
class IAITO_EXPORT NameListModel : public AddressableItemModel<QAbstractProxyModel>
|
||||
{
|
||||
public:
|
||||
enum {
|
||||
GroupPathRole = Qt::UserRole + 0x1000,
|
||||
// A complete, HTML-escaped name tooltip supplied by models with alternate spellings.
|
||||
NameToolTipRole
|
||||
};
|
||||
|
||||
NameListModel(AddressableItemModelI *source, int nameColumn, QObject *parent = nullptr);
|
||||
~NameListModel() override;
|
||||
|
||||
void setSourceModel(QAbstractItemModel *source) override;
|
||||
QModelIndex mapToSource(const QModelIndex &index) const override;
|
||||
QModelIndex mapFromSource(const QModelIndex &index) const override;
|
||||
QModelIndex index(int row, int column, const QModelIndex &parent = {}) const override;
|
||||
QModelIndex parent(const QModelIndex &index) const override;
|
||||
int rowCount(const QModelIndex &parent = {}) const override;
|
||||
int columnCount(const QModelIndex &parent = {}) const override;
|
||||
bool hasChildren(const QModelIndex &parent = {}) const override;
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
QVariant headerData(
|
||||
int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||
void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override;
|
||||
RVA address(const QModelIndex &index) const override;
|
||||
QString name(const QModelIndex &index) const override;
|
||||
|
||||
void setCompact(bool enabled);
|
||||
void setHierarchy(bool enabled);
|
||||
bool isHierarchy() const { return hierarchy; }
|
||||
QModelIndex groupIndex(const QString &path) const;
|
||||
|
||||
private:
|
||||
struct Node
|
||||
{
|
||||
Node *parent = nullptr;
|
||||
int row = 0;
|
||||
QModelIndex source;
|
||||
QString label;
|
||||
QString fullName;
|
||||
bool named = false;
|
||||
QString groupPath;
|
||||
std::vector<std::unique_ptr<Node>> children;
|
||||
};
|
||||
|
||||
AddressableItemModelI *sourceProvider = nullptr;
|
||||
int nameColumn;
|
||||
bool compact = false;
|
||||
bool hierarchy = false;
|
||||
bool resetting = false;
|
||||
int filterChangeDepth = 0;
|
||||
Node root;
|
||||
QHash<QModelIndex, Node *> sourceNodes;
|
||||
QHash<QString, Node *> groups;
|
||||
|
||||
Node *node(const QModelIndex &index) const;
|
||||
QModelIndex nodeIndex(Node *node, int column = 0) const;
|
||||
void beginRebuild();
|
||||
void endRebuild();
|
||||
void rebuild();
|
||||
void collapseSingleGroups(Node *parent);
|
||||
void appendSource(Node *parent, const QModelIndex &source, const QString &label, bool named);
|
||||
void sourceDataChanged(const QModelIndex &first, const QModelIndex &last);
|
||||
void namesChanged(Node *parent);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,179 @@
|
||||
#include "PreviewTooltip.h"
|
||||
|
||||
#include "common/Configuration.h"
|
||||
#include "core/Iaito.h"
|
||||
|
||||
#include <QFont>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonValue>
|
||||
#include <QRegularExpression>
|
||||
#include <QWidget>
|
||||
|
||||
namespace {
|
||||
|
||||
static const int kMaxTooltipDisasmPreviewLines = 10;
|
||||
static const int kMaxTooltipHexdumpBytes = 64;
|
||||
|
||||
struct RegisterPreview
|
||||
{
|
||||
QString name;
|
||||
QString value;
|
||||
RVA address = RVA_INVALID;
|
||||
};
|
||||
|
||||
QString normalizedRegisterToken(const QString &token)
|
||||
{
|
||||
static const QRegularExpression trimRegExp(QStringLiteral(R"(^[^\w]+|[^\w]+$)"));
|
||||
return QString(token).remove(trimRegExp).toLower();
|
||||
}
|
||||
|
||||
QString registerValueString(const QJsonValue &value, RVA *address)
|
||||
{
|
||||
QString valueString;
|
||||
bool ok = false;
|
||||
if (value.isString()) {
|
||||
valueString = value.toString().trimmed();
|
||||
if (address) {
|
||||
*address = Core()->math(valueString);
|
||||
}
|
||||
} else {
|
||||
const RVA valueAddress = value.toVariant().toULongLong(&ok);
|
||||
if (ok) {
|
||||
valueString = RAddressString(valueAddress);
|
||||
if (address) {
|
||||
*address = valueAddress;
|
||||
}
|
||||
}
|
||||
}
|
||||
return valueString;
|
||||
}
|
||||
|
||||
bool registerPreviewForToken(const QString &token, RegisterPreview *preview)
|
||||
{
|
||||
const QString needle = normalizedRegisterToken(token);
|
||||
if (needle.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const QVector<RegisterRefValueDescription> refs = Core()->getRegisterRefValues();
|
||||
for (const RegisterRefValueDescription ® : refs) {
|
||||
if (reg.name.toLower() != needle || reg.value.trimmed().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (preview) {
|
||||
preview->name = reg.name;
|
||||
preview->value = reg.value.trimmed();
|
||||
preview->address = Core()->math(preview->value);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const QJsonObject registers = Core()->getRegisterValues().object();
|
||||
for (const QString &name : registers.keys()) {
|
||||
if (name.toLower() != needle) {
|
||||
continue;
|
||||
}
|
||||
|
||||
RVA address = RVA_INVALID;
|
||||
const QString value = registerValueString(registers.value(name), &address);
|
||||
if (value.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
if (preview) {
|
||||
preview->name = name;
|
||||
preview->value = value;
|
||||
preview->address = address;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
QString previewContainerStart()
|
||||
{
|
||||
const QFont &fnt = Config()->getFont();
|
||||
return QStringLiteral(
|
||||
"<html><div style=\"font-family: %1; font-size: %2pt; white-space: nowrap;\">")
|
||||
.arg(fnt.family().toHtmlEscaped())
|
||||
.arg(qMax(6, fnt.pointSize() - 1));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
QString PreviewTooltip::buildAddressPreview(RVA address)
|
||||
{
|
||||
if (address == RVA_INVALID) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString mapName = Core()->cmd(QStringLiteral("dm.@ %1").arg(address)).trimmed();
|
||||
QStringList disasmPreview
|
||||
= Core()->getDisassemblyPreview(address, kMaxTooltipDisasmPreviewLines);
|
||||
QString hexPreview = Core()->getHexdumpPreview(address, kMaxTooltipHexdumpBytes);
|
||||
|
||||
if (mapName.isEmpty() && disasmPreview.isEmpty() && hexPreview.isEmpty()) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString tip = previewContainerStart();
|
||||
if (!mapName.isEmpty()) {
|
||||
tip += QStringLiteral("<div style=\"margin-bottom: 6px;\"><strong>Map</strong>: %1</div>")
|
||||
.arg(mapName.toHtmlEscaped());
|
||||
}
|
||||
|
||||
if (!disasmPreview.isEmpty()) {
|
||||
tip += QStringLiteral(
|
||||
"<div style=\"margin-bottom: 6px;\"><strong>Disassembly</strong>:<br>%1</div>")
|
||||
.arg(disasmPreview.join("<br>"));
|
||||
}
|
||||
|
||||
if (!hexPreview.isEmpty()) {
|
||||
tip += QStringLiteral("<div><strong>Hexdump</strong>:<br>%1</div>").arg(hexPreview);
|
||||
}
|
||||
|
||||
tip += QStringLiteral("</div></html>");
|
||||
return tip;
|
||||
}
|
||||
|
||||
QString PreviewTooltip::buildRegisterPreview(const QString &token)
|
||||
{
|
||||
RegisterPreview preview;
|
||||
if (!registerPreviewForToken(token, &preview)) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString tip = previewContainerStart();
|
||||
tip += QStringLiteral(
|
||||
"<div style=\"margin-bottom: 6px;\"><strong>Register</strong>: %1 = %2</div>")
|
||||
.arg(preview.name.toHtmlEscaped())
|
||||
.arg(preview.value.toHtmlEscaped());
|
||||
|
||||
QString addressPreview = buildAddressPreview(preview.address);
|
||||
if (!addressPreview.isEmpty()) {
|
||||
addressPreview.remove(QRegularExpression(QStringLiteral(R"(^<html><div[^>]*>)")));
|
||||
addressPreview.chop(QStringLiteral("</div></html>").size());
|
||||
if (!addressPreview.isEmpty()) {
|
||||
tip += addressPreview;
|
||||
}
|
||||
}
|
||||
|
||||
tip += QStringLiteral("</div></html>");
|
||||
return tip;
|
||||
}
|
||||
|
||||
void PreviewTooltip::applyStyleSheet(QWidget *widget, int maxWidth)
|
||||
{
|
||||
if (!widget) {
|
||||
return;
|
||||
}
|
||||
|
||||
widget->setStyleSheet(
|
||||
QStringLiteral(
|
||||
"QToolTip { border: 1px solid %3; border-radius: 6px; max-width: %1px;"
|
||||
" padding: 6px; opacity: 255; background-color: %2; color: %3; }")
|
||||
.arg(maxWidth)
|
||||
.arg(Config()->getColor("gui.tooltip.background").name())
|
||||
.arg(Config()->getColor("gui.tooltip.foreground").name()));
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#ifndef PREVIEWTOOLTIP_H
|
||||
#define PREVIEWTOOLTIP_H
|
||||
|
||||
#include "core/IaitoCommon.h"
|
||||
|
||||
#include <QString>
|
||||
|
||||
class QWidget;
|
||||
|
||||
namespace PreviewTooltip {
|
||||
|
||||
QString buildAddressPreview(RVA address);
|
||||
QString buildRegisterPreview(const QString &token);
|
||||
void applyStyleSheet(QWidget *widget, int maxWidth = 420);
|
||||
|
||||
} // namespace PreviewTooltip
|
||||
|
||||
#endif // PREVIEWTOOLTIP_H
|
||||
@@ -35,10 +35,14 @@ void ProgressIndicator::setAnimating(bool animating)
|
||||
|
||||
void ProgressIndicator::updateAnimationTimer()
|
||||
{
|
||||
bool shouldBeAnimating = animating && progressIndicatorVisible;
|
||||
if (shouldBeAnimating && !animationTimerId) {
|
||||
animationTimerId = startTimer(timerInterval);
|
||||
} else {
|
||||
const bool shouldBeAnimating = animating && progressIndicatorVisible;
|
||||
if (shouldBeAnimating) {
|
||||
if (!animationTimerId) {
|
||||
animationTimerId = startTimer(timerInterval);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (animationTimerId) {
|
||||
killTimer(animationTimerId);
|
||||
animationTimerId = 0;
|
||||
}
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
|
||||
#ifdef IAITO_ENABLE_PYTHON
|
||||
|
||||
#include "PythonAPI.h"
|
||||
#include "core/Iaito.h"
|
||||
|
||||
#include "IaitoConfig.h"
|
||||
|
||||
#include <QFile>
|
||||
|
||||
PyObject *api_version(PyObject *self, PyObject *null)
|
||||
{
|
||||
Q_UNUSED(self)
|
||||
Q_UNUSED(null)
|
||||
return PyUnicode_FromString(IAITO_VERSION_FULL);
|
||||
}
|
||||
|
||||
PyObject *api_cmd(PyObject *self, PyObject *args)
|
||||
{
|
||||
Q_UNUSED(self);
|
||||
char *command;
|
||||
char *result = (char *) "";
|
||||
QString cmdRes;
|
||||
QByteArray cmdBytes;
|
||||
if (PyArg_ParseTuple(args, "s:command", &command)) {
|
||||
cmdRes = Core()->cmd(command);
|
||||
cmdBytes = cmdRes.toLocal8Bit();
|
||||
result = cmdBytes.data();
|
||||
}
|
||||
return PyUnicode_FromString(result);
|
||||
}
|
||||
|
||||
PyObject *api_refresh(PyObject *self, PyObject *args)
|
||||
{
|
||||
Q_UNUSED(self);
|
||||
Q_UNUSED(args);
|
||||
Core()->triggerRefreshAll();
|
||||
return Py_None;
|
||||
}
|
||||
|
||||
PyObject *api_message(PyObject *self, PyObject *args, PyObject *kwargs)
|
||||
{
|
||||
Q_UNUSED(self);
|
||||
char *message;
|
||||
int debug = 0;
|
||||
static const char *kwlist[] = {"", "debug", NULL};
|
||||
if (!PyArg_ParseTupleAndKeywords(
|
||||
args, kwargs, "s|i", const_cast<char **>(kwlist), &message, &debug)) {
|
||||
return NULL;
|
||||
}
|
||||
Core()->message(QString(message), debug);
|
||||
Py_INCREF(Py_None);
|
||||
return Py_None;
|
||||
}
|
||||
|
||||
PyMethodDef IaitoMethods[]
|
||||
= {{"version", api_version, METH_NOARGS, "Returns Iaito current version"},
|
||||
{"cmd", api_cmd, METH_VARARGS, "Execute a command inside Iaito"},
|
||||
{"refresh", api_refresh, METH_NOARGS, "Refresh Iaito widgets"},
|
||||
{"message",
|
||||
(PyCFunction) (void *) /* don't remove this double cast! */ api_message,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
"Print message"},
|
||||
{NULL, NULL, 0, NULL}};
|
||||
|
||||
PyModuleDef IaitoModule
|
||||
= {PyModuleDef_HEAD_INIT, "_iaito", NULL, -1, IaitoMethods, NULL, NULL, NULL, NULL};
|
||||
|
||||
PyObject *PyInit_api()
|
||||
{
|
||||
return PyModule_Create(&IaitoModule);
|
||||
}
|
||||
|
||||
#endif // IAITO_ENABLE_PYTHON
|
||||
@@ -1,13 +0,0 @@
|
||||
#ifndef PYTHONAPI_H
|
||||
#define PYTHONAPI_H
|
||||
|
||||
#ifdef IAITO_ENABLE_PYTHON
|
||||
|
||||
#define Py_LIMITED_API 0x03050000
|
||||
#include <Python.h>
|
||||
|
||||
PyObject *PyInit_api();
|
||||
|
||||
#endif
|
||||
|
||||
#endif // PYTHONAPI_H
|
||||
@@ -1,185 +0,0 @@
|
||||
#ifdef IAITO_ENABLE_PYTHON
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include "Iaito.h"
|
||||
#include "PythonAPI.h"
|
||||
#include "PythonManager.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
|
||||
#ifdef IAITO_ENABLE_PYTHON_BINDINGS
|
||||
#include <pyside.h>
|
||||
#include <shiboken.h>
|
||||
#include <signalmanager.h>
|
||||
#endif
|
||||
|
||||
#include "QtResImporter.h"
|
||||
|
||||
static PythonManager *uniqueInstance = nullptr;
|
||||
|
||||
PythonManager *PythonManager::getInstance()
|
||||
{
|
||||
if (!uniqueInstance) {
|
||||
uniqueInstance = new PythonManager();
|
||||
}
|
||||
return uniqueInstance;
|
||||
}
|
||||
|
||||
PythonManager::PythonManager() {}
|
||||
|
||||
PythonManager::~PythonManager() {}
|
||||
|
||||
void PythonManager::initPythonHome()
|
||||
{
|
||||
#if defined(APPIMAGE) || defined(MACOS_PYTHON_FRAMEWORK_BUNDLED)
|
||||
if (customPythonHome.isNull()) {
|
||||
auto pythonHomeDir = QDir(QCoreApplication::applicationDirPath());
|
||||
#ifdef APPIMAGE
|
||||
// Executable is in appdir/bin
|
||||
pythonHomeDir.cdUp();
|
||||
qInfo() << "Setting PYTHONHOME =" << pythonHomeDir.absolutePath() << " for AppImage.";
|
||||
#else // MACOS_PYTHON_FRAMEWORK_BUNDLED \
|
||||
// @executable_path/../Frameworks/Python.framework/Versions/Current
|
||||
pythonHomeDir.cd("../Frameworks/Python.framework/Versions/Current");
|
||||
qInfo() << "Setting PYTHONHOME =" << pythonHomeDir.absolutePath()
|
||||
<< " for macOS Application Bundle.";
|
||||
#endif
|
||||
customPythonHome = pythonHomeDir.absolutePath();
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!customPythonHome.isNull()) {
|
||||
qInfo() << "PYTHONHOME =" << customPythonHome;
|
||||
pythonHome = Py_DecodeLocale(customPythonHome.toLocal8Bit().constData(), nullptr);
|
||||
Py_SetPythonHome(pythonHome);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef IAITO_ENABLE_PYTHON_BINDINGS
|
||||
extern "C" PyObject *PyInit_IaitoBindings();
|
||||
#endif
|
||||
|
||||
void PythonManager::initialize()
|
||||
{
|
||||
initPythonHome();
|
||||
|
||||
PyImport_AppendInittab("_iaito", &PyInit_api);
|
||||
PyImport_AppendInittab("_qtres", &PyInit_qtres);
|
||||
#ifdef IAITO_ENABLE_PYTHON_BINDINGS
|
||||
PyImport_AppendInittab("IaitoBindings", &PyInit_IaitoBindings);
|
||||
#endif
|
||||
Py_Initialize();
|
||||
PyEval_InitThreads();
|
||||
pyThreadStateCounter = 1; // we have the thread now => 1
|
||||
|
||||
RegQtResImporter();
|
||||
|
||||
saveThread();
|
||||
}
|
||||
|
||||
#ifdef IAITO_ENABLE_PYTHON_BINDINGS
|
||||
static void pySideDestructionVisitor(SbkObject *pyObj, void *data)
|
||||
{
|
||||
void **realData = reinterpret_cast<void **>(data);
|
||||
auto pyQApp = reinterpret_cast<SbkObject *>(realData[0]);
|
||||
auto pyQObjectType = reinterpret_cast<PyTypeObject *>(realData[1]);
|
||||
|
||||
if (pyObj == pyQApp || !PyObject_TypeCheck(pyObj, pyQObjectType)) {
|
||||
return;
|
||||
}
|
||||
if (!Shiboken::Object::hasOwnership(pyObj) || !Shiboken::Object::isValid(pyObj, false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const char *reprStr = "";
|
||||
PyObject *repr = PyObject_Repr(reinterpret_cast<PyObject *>(pyObj));
|
||||
PyObject *reprBytes;
|
||||
if (repr) {
|
||||
reprBytes = PyUnicode_AsUTF8String(repr);
|
||||
reprStr = PyBytes_AsString(reprBytes);
|
||||
}
|
||||
qWarning() << "Warning: QObject from Python remaining (leaked from plugin?):" << reprStr;
|
||||
if (repr) {
|
||||
Py_DecRef(reprBytes);
|
||||
Py_DecRef(repr);
|
||||
}
|
||||
|
||||
Shiboken::Object::setValidCpp(pyObj, false);
|
||||
Py_BEGIN_ALLOW_THREADS Shiboken::callCppDestructor<QObject>(
|
||||
Shiboken::Object::cppPointer(pyObj, pyQObjectType));
|
||||
Py_END_ALLOW_THREADS
|
||||
};
|
||||
#endif
|
||||
|
||||
void PythonManager::shutdown()
|
||||
{
|
||||
emit willShutDown();
|
||||
|
||||
restoreThread();
|
||||
|
||||
#ifdef IAITO_ENABLE_PYTHON_BINDINGS
|
||||
// This is necessary to prevent a segfault when the IaitoCore instance is
|
||||
// deleted after the Shiboken::BindingManager
|
||||
Core()->setProperty("_PySideInvalidatePtr", QVariant());
|
||||
|
||||
// see PySide::destroyQCoreApplication()
|
||||
PySide::SignalManager::instance().clear();
|
||||
Shiboken::BindingManager &bm = Shiboken::BindingManager::instance();
|
||||
SbkObject *pyQApp = bm.retrieveWrapper(QCoreApplication::instance());
|
||||
PyTypeObject *pyQObjectType = Shiboken::Conversions::getPythonTypeObject("QObject*");
|
||||
void *data[2] = {pyQApp, pyQObjectType};
|
||||
bm.visitAllPyObjects(&pySideDestructionVisitor, &data);
|
||||
|
||||
PySide::runCleanupFunctions();
|
||||
#endif
|
||||
|
||||
if (pythonHome) {
|
||||
PyMem_Free(pythonHome);
|
||||
}
|
||||
|
||||
Py_Finalize();
|
||||
}
|
||||
|
||||
void PythonManager::addPythonPath(char *path)
|
||||
{
|
||||
restoreThread();
|
||||
|
||||
PyObject *sysModule = PyImport_ImportModule("sys");
|
||||
if (!sysModule) {
|
||||
return;
|
||||
}
|
||||
PyObject *pythonPath = PyObject_GetAttrString(sysModule, "path");
|
||||
if (!pythonPath) {
|
||||
return;
|
||||
}
|
||||
PyObject *append = PyObject_GetAttrString(pythonPath, "append");
|
||||
if (!append) {
|
||||
return;
|
||||
}
|
||||
PyEval_CallFunction(append, "(s)", path);
|
||||
|
||||
saveThread();
|
||||
}
|
||||
|
||||
void PythonManager::restoreThread()
|
||||
{
|
||||
pyThreadStateCounter++;
|
||||
if (pyThreadStateCounter == 1 && pyThreadState) {
|
||||
PyEval_RestoreThread(pyThreadState);
|
||||
}
|
||||
}
|
||||
|
||||
void PythonManager::saveThread()
|
||||
{
|
||||
pyThreadStateCounter--;
|
||||
assert(pyThreadStateCounter >= 0);
|
||||
if (pyThreadStateCounter == 0) {
|
||||
pyThreadState = PyEval_SaveThread();
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,60 +0,0 @@
|
||||
#ifndef PYTHONMANAGER_H
|
||||
#define PYTHONMANAGER_H
|
||||
|
||||
#ifdef IAITO_ENABLE_PYTHON
|
||||
|
||||
#include <QObject>
|
||||
|
||||
typedef struct _ts PyThreadState;
|
||||
typedef struct _object PyObject;
|
||||
|
||||
class PythonManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
static PythonManager *getInstance();
|
||||
|
||||
PythonManager();
|
||||
~PythonManager();
|
||||
|
||||
void setPythonHome(const QString &pythonHome) { customPythonHome = pythonHome; }
|
||||
|
||||
void initPythonHome();
|
||||
void initialize();
|
||||
void shutdown();
|
||||
|
||||
void addPythonPath(char *path);
|
||||
|
||||
void restoreThread();
|
||||
void saveThread();
|
||||
|
||||
/**
|
||||
* @brief RAII Helper class to call restoreThread() and saveThread()
|
||||
* automatically
|
||||
*
|
||||
* As long as an object of this class is in scope, the Python thread will
|
||||
* remain restored.
|
||||
*/
|
||||
class ThreadHolder
|
||||
{
|
||||
public:
|
||||
ThreadHolder() { getInstance()->restoreThread(); }
|
||||
~ThreadHolder() { getInstance()->saveThread(); }
|
||||
};
|
||||
|
||||
signals:
|
||||
void willShutDown();
|
||||
|
||||
private:
|
||||
QString customPythonHome;
|
||||
wchar_t *pythonHome = nullptr;
|
||||
PyThreadState *pyThreadState = nullptr;
|
||||
int pyThreadStateCounter = 0;
|
||||
};
|
||||
|
||||
#define Python() (PythonManager::getInstance())
|
||||
|
||||
#endif // IAITO_ENABLE_PYTHON
|
||||
|
||||
#endif // PYTHONMANAGER_H
|
||||
@@ -1,87 +0,0 @@
|
||||
#ifdef IAITO_ENABLE_PYTHON
|
||||
|
||||
#define Py_LIMITED_API 0x03050000
|
||||
#include <Python.h>
|
||||
|
||||
#include "QtResImporter.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QFile>
|
||||
|
||||
bool QtResExists(const char *name, QFile &file)
|
||||
{
|
||||
QString fname = QString::asprintf(":/python/%s.py", name);
|
||||
file.setFileName(fname);
|
||||
return file.exists();
|
||||
}
|
||||
|
||||
PyObject *QtResGetCode(const char *name)
|
||||
{
|
||||
QFile moduleFile;
|
||||
|
||||
if (!QtResExists(name, moduleFile)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
moduleFile.open(QIODevice::ReadOnly);
|
||||
QByteArray data = moduleFile.readAll();
|
||||
moduleFile.close();
|
||||
|
||||
PyObject *codeObject = Py_CompileString(
|
||||
data.constData(), moduleFile.fileName().toLocal8Bit().constData(), Py_file_input);
|
||||
if (!codeObject) {
|
||||
qWarning() << "Couldn't compile " << moduleFile.fileName();
|
||||
}
|
||||
return codeObject;
|
||||
}
|
||||
|
||||
PyObject *QtResImport(const char *name)
|
||||
{
|
||||
PyObject *codeObject = QtResGetCode(name);
|
||||
if (!codeObject) {
|
||||
return nullptr;
|
||||
}
|
||||
PyObject *module = PyImport_ExecCodeModule(name, codeObject);
|
||||
if (!module) {
|
||||
PyErr_Print();
|
||||
}
|
||||
Py_DECREF(codeObject);
|
||||
return module;
|
||||
}
|
||||
|
||||
PyObject *qtres_exists(PyObject *self, PyObject *args)
|
||||
{
|
||||
Q_UNUSED(self)
|
||||
char *name;
|
||||
QFile resFile;
|
||||
if (!PyArg_ParseTuple(args, "s", &name))
|
||||
return NULL;
|
||||
return PyBool_FromLong(QtResExists(name, resFile));
|
||||
}
|
||||
|
||||
PyObject *qtres_get_code(PyObject *self, PyObject *args)
|
||||
{
|
||||
Q_UNUSED(self)
|
||||
char *name;
|
||||
if (!PyArg_ParseTuple(args, "s", &name))
|
||||
return NULL;
|
||||
PyObject *ret = QtResGetCode(name);
|
||||
if (ret)
|
||||
return ret;
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
PyMethodDef QtResMethods[]
|
||||
= {{"exists", qtres_exists, METH_VARARGS, NULL},
|
||||
{"get_code", qtres_get_code, METH_VARARGS, NULL},
|
||||
{NULL, NULL, 0, NULL}};
|
||||
|
||||
PyModuleDef QtResModule
|
||||
= {PyModuleDef_HEAD_INIT, "_qtres", NULL, -1, QtResMethods, NULL, NULL, NULL, NULL};
|
||||
|
||||
PyObject *PyInit_qtres()
|
||||
{
|
||||
return PyModule_Create(&QtResModule);
|
||||
}
|
||||
|
||||
#endif // IAITO_ENABLE_PYTHON
|
||||
@@ -1,14 +0,0 @@
|
||||
#ifndef QTRESIMPORTER_H
|
||||
#define QTRESIMPORTER_H
|
||||
|
||||
#ifdef IAITO_ENABLE_PYTHON
|
||||
|
||||
PyObject *PyInit_qtres();
|
||||
|
||||
PyObject *QtResImport(const char *name);
|
||||
|
||||
#define RegQtResImporter() Py_DecRef(QtResImport("reg_qtres_importer"))
|
||||
|
||||
#endif // IAITO_ENABLE_PYTHON
|
||||
|
||||
#endif // QTRESIMPORTER_H
|
||||
@@ -0,0 +1,460 @@
|
||||
#include "R2AiDecompiler.h"
|
||||
#include "CodeMetaRange.h"
|
||||
#include "Iaito.h"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonParseError>
|
||||
#include <QRegularExpression>
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int R2AI_POLL_INTERVAL_MS = 1000;
|
||||
|
||||
QString stripAnsi(QString text)
|
||||
{
|
||||
static const QRegularExpression ansiPattern(QStringLiteral("\x1b\\[[0-?]*[ -/]*[@-~]"));
|
||||
text.remove(ansiPattern);
|
||||
return text;
|
||||
}
|
||||
|
||||
QString normalizeNewlines(QString text)
|
||||
{
|
||||
text.replace(QStringLiteral("\r\n"), QStringLiteral("\n"));
|
||||
text.replace(QLatin1Char('\r'), QLatin1Char('\n'));
|
||||
return text;
|
||||
}
|
||||
|
||||
QJsonDocument parseJsonObjectResult(const QString &result)
|
||||
{
|
||||
QByteArray json = result.toUtf8().trimmed();
|
||||
const int begin = json.indexOf('{');
|
||||
const int end = json.lastIndexOf('}');
|
||||
if (begin > 0 && end > begin) {
|
||||
json = json.mid(begin, end - begin + 1);
|
||||
}
|
||||
|
||||
QJsonParseError error;
|
||||
QJsonDocument document = QJsonDocument::fromJson(json, &error);
|
||||
if (error.error != QJsonParseError::NoError || !document.isObject()) {
|
||||
return QJsonDocument();
|
||||
}
|
||||
return document;
|
||||
}
|
||||
|
||||
int queuedTaskId(const QString &result)
|
||||
{
|
||||
static const QRegularExpression queuedPattern(
|
||||
QStringLiteral("\\[async\\]\\s+task\\s+(\\d+)\\s+queued"));
|
||||
const QRegularExpressionMatch match = queuedPattern.match(stripAnsi(result));
|
||||
return match.hasMatch() ? match.captured(1).toInt() : 0;
|
||||
}
|
||||
|
||||
bool isFinalState(const QString &state)
|
||||
{
|
||||
return state == QLatin1String("complete") || state == QLatin1String("error")
|
||||
|| state == QLatin1String("cancelled") || state == QLatin1String("stopped");
|
||||
}
|
||||
|
||||
void setCodeString(RCodeMeta *code, const QString &text)
|
||||
{
|
||||
QByteArray bytes = text.toUtf8();
|
||||
free(code->code);
|
||||
code->code = strdup(bytes.constData());
|
||||
}
|
||||
|
||||
RCodeMeta *copyCodeMeta(RCodeMeta *source)
|
||||
{
|
||||
RCodeMeta *copy = r_codemeta_new("");
|
||||
free(copy->code);
|
||||
copy->code = strdup(source && source->code ? source->code : "");
|
||||
if (!source) {
|
||||
return copy;
|
||||
}
|
||||
|
||||
#if R2_ABIVERSION >= 40
|
||||
RCodeMetaItem *oldItem;
|
||||
R_VEC_FOREACH(&source->annotations, oldItem)
|
||||
{
|
||||
#else
|
||||
void *iter;
|
||||
r_vector_foreach(&source->annotations, iter)
|
||||
{
|
||||
RCodeMetaItem *oldItem = reinterpret_cast<RCodeMetaItem *>(iter);
|
||||
#endif
|
||||
RCodeMetaItem *newItem = r_codemeta_item_new();
|
||||
r_codemeta_item_copy(newItem, oldItem);
|
||||
r_codemeta_add_item(copy, newItem);
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
void addOffsetAnnotation(RCodeMeta *code, size_t start, size_t end, RVA offset)
|
||||
{
|
||||
if (!code || offset == RVA_INVALID || end <= start) {
|
||||
return;
|
||||
}
|
||||
RCodeMetaItem *mi = r_codemeta_item_new();
|
||||
mi->start = start;
|
||||
mi->end = end;
|
||||
mi->type = R_CODEMETA_TYPE_OFFSET;
|
||||
mi->offset.offset = offset;
|
||||
r_codemeta_add_item(code, mi);
|
||||
}
|
||||
|
||||
RCodeMeta *buildCodeMetaFromJson(const QJsonDocument &document)
|
||||
{
|
||||
if (!document.isObject() && !document.isArray()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RCodeMeta *code = r_codemeta_new("");
|
||||
QString codeString;
|
||||
|
||||
auto appendLine = [&code, &codeString](const QJsonObject &obj) {
|
||||
QString text = obj.value(QStringLiteral("str")).toString();
|
||||
if (text.isEmpty()) {
|
||||
text = obj.value(QStringLiteral("code")).toString();
|
||||
}
|
||||
if (text.isEmpty()) {
|
||||
text = obj.value(QStringLiteral("text")).toString();
|
||||
}
|
||||
const size_t start = static_cast<size_t>(codeString.size());
|
||||
codeString.append(text);
|
||||
codeString.append(QLatin1Char('\n'));
|
||||
const size_t end = static_cast<size_t>(codeString.size());
|
||||
auto offset = codeMetaAddressFromJson(obj.value(QStringLiteral("offset")));
|
||||
if (!offset) {
|
||||
offset = codeMetaAddressFromJson(obj.value(QStringLiteral("addr")));
|
||||
}
|
||||
if (offset) {
|
||||
addOffsetAnnotation(code, start, end, *offset);
|
||||
}
|
||||
};
|
||||
|
||||
if (document.isArray()) {
|
||||
for (const QJsonValue &value : document.array()) {
|
||||
if (value.isObject()) {
|
||||
appendLine(value.toObject());
|
||||
}
|
||||
}
|
||||
setCodeString(code, codeString);
|
||||
return code;
|
||||
}
|
||||
|
||||
const QJsonObject root = document.object();
|
||||
if (root.contains(QStringLiteral("code"))) {
|
||||
codeString = root.value(QStringLiteral("code")).toString();
|
||||
for (const QJsonValue &value : root.value(QStringLiteral("annotations")).toArray()) {
|
||||
const QJsonObject obj = value.toObject();
|
||||
if (obj.value(QStringLiteral("type")).toString() != QLatin1String("offset")) {
|
||||
continue;
|
||||
}
|
||||
auto range = codeMetaOffsetRangeFromJson(obj, codeString);
|
||||
auto offset = codeMetaAddressFromJson(obj.value(QStringLiteral("offset")));
|
||||
if (range && offset) {
|
||||
addOffsetAnnotation(code, range->start, range->end, *offset);
|
||||
}
|
||||
}
|
||||
setCodeString(code, codeString);
|
||||
return code;
|
||||
}
|
||||
|
||||
const QJsonArray lines = root.value(QStringLiteral("lines")).toArray();
|
||||
if (!lines.isEmpty()) {
|
||||
for (const QJsonValue &value : lines) {
|
||||
if (value.isObject()) {
|
||||
appendLine(value.toObject());
|
||||
}
|
||||
}
|
||||
setCodeString(code, codeString);
|
||||
return code;
|
||||
}
|
||||
|
||||
r_codemeta_free(code);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool parseLineAddress(const QString &line, RVA *address, QString *withoutAddress)
|
||||
{
|
||||
static const QRegularExpression addressPattern(QStringLiteral(
|
||||
"^\\s*(/\\*|//|#|\\[)?\\s*(0x[0-9a-fA-F]+)\\s*(\\*/|\\])?\\s*([:\\|\\-])?\\s*(.*)$"));
|
||||
const QRegularExpressionMatch match = addressPattern.match(line);
|
||||
if (!match.hasMatch()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const QString openMarker = match.captured(1);
|
||||
const QString closeMarker = match.captured(3);
|
||||
const QString separator = match.captured(4);
|
||||
const QString addressText = match.captured(2);
|
||||
if (openMarker.isEmpty() && closeMarker.isEmpty() && separator.isEmpty()
|
||||
&& addressText.size() < 8) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ok = false;
|
||||
qulonglong parsed = addressText.toULongLong(&ok, 0);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*address = parsed;
|
||||
*withoutAddress = match.captured(5);
|
||||
return true;
|
||||
}
|
||||
|
||||
RCodeMeta *buildCodeMetaFromText(QString output)
|
||||
{
|
||||
output = normalizeNewlines(stripAnsi(output)).trimmed();
|
||||
|
||||
const QJsonDocument document = QJsonDocument::fromJson(output.toUtf8());
|
||||
if (RCodeMeta *jsonCode = buildCodeMetaFromJson(document)) {
|
||||
return jsonCode;
|
||||
}
|
||||
|
||||
RCodeMeta *code = r_codemeta_new("");
|
||||
QString codeString;
|
||||
const QStringList lines = output.split(QLatin1Char('\n'));
|
||||
for (const QString &rawLine : lines) {
|
||||
RVA address = RVA_INVALID;
|
||||
QString line = rawLine;
|
||||
const bool hasAddress = parseLineAddress(rawLine, &address, &line);
|
||||
|
||||
const size_t start = static_cast<size_t>(codeString.size());
|
||||
codeString.append(line);
|
||||
codeString.append(QLatin1Char('\n'));
|
||||
const size_t end = static_cast<size_t>(codeString.size());
|
||||
if (hasAddress) {
|
||||
addOffsetAnnotation(code, start, end, address);
|
||||
}
|
||||
}
|
||||
|
||||
setCodeString(code, codeString);
|
||||
return code;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
R2AiDecompiler::R2AiDecompiler(QObject *parent)
|
||||
: Decompiler("r2ai", "r2ai", parent)
|
||||
, pollTimer(new QTimer(this))
|
||||
{
|
||||
pollTimer->setInterval(R2AI_POLL_INTERVAL_MS);
|
||||
connect(pollTimer, &QTimer::timeout, this, &R2AiDecompiler::pollAsyncTask);
|
||||
}
|
||||
|
||||
R2AiDecompiler::~R2AiDecompiler()
|
||||
{
|
||||
pollTimer->stop();
|
||||
if (taskId > 0) {
|
||||
Core()->cmd(QStringLiteral("r2ai -sk %1").arg(taskId));
|
||||
restoreAsyncPurgeConfig();
|
||||
}
|
||||
clearCache();
|
||||
}
|
||||
|
||||
bool R2AiDecompiler::isAvailable()
|
||||
{
|
||||
return Core()->cmdList("e cmd.pdc=?").contains(QStringLiteral("r2ai"));
|
||||
}
|
||||
|
||||
QString R2AiDecompiler::cacheKeyForAddr(RVA addr) const
|
||||
{
|
||||
RVA functionAddr = Core()->getFunctionStart(addr);
|
||||
if (functionAddr == RVA_INVALID) {
|
||||
functionAddr = addr;
|
||||
}
|
||||
return QStringLiteral("%1|%2|%3|%4|%5|%6|%7")
|
||||
.arg(RAddressString(functionAddr))
|
||||
.arg(Core()->getConfig(QStringLiteral("r2ai.api")))
|
||||
.arg(Core()->getConfig(QStringLiteral("r2ai.model")))
|
||||
.arg(Core()->getConfig(QStringLiteral("r2ai.prompt")))
|
||||
.arg(Core()->getConfig(QStringLiteral("r2ai.cmds")))
|
||||
.arg(Core()->getConfig(QStringLiteral("r2ai.lang")))
|
||||
.arg(Core()->getConfig(QStringLiteral("r2ai.system")));
|
||||
}
|
||||
|
||||
QString R2AiDecompiler::decompileCommand(RVA addr) const
|
||||
{
|
||||
if (!offsetCommandChecked) {
|
||||
const QString help = Core()->cmd(QStringLiteral("r2ai -h"));
|
||||
offsetCommandAvailable = help.contains(QStringLiteral(" -do"));
|
||||
offsetCommandChecked = true;
|
||||
}
|
||||
const QString mode = offsetCommandAvailable ? QStringLiteral("-do") : QStringLiteral("-d");
|
||||
return QStringLiteral("r2ai %1 @ %2").arg(mode, QString::number(addr));
|
||||
}
|
||||
|
||||
void R2AiDecompiler::ensureAsyncConfig()
|
||||
{
|
||||
if (!asyncPurgeSaved) {
|
||||
previousAsyncPurge = Core()->getConfigb(QStringLiteral("r2ai.async.purge"));
|
||||
asyncPurgeSaved = true;
|
||||
}
|
||||
Core()->setConfig(QStringLiteral("r2ai.async"), true);
|
||||
Core()->setConfig(QStringLiteral("r2ai.async.purge"), false);
|
||||
}
|
||||
|
||||
void R2AiDecompiler::restoreAsyncPurgeConfig()
|
||||
{
|
||||
if (!asyncPurgeSaved) {
|
||||
return;
|
||||
}
|
||||
Core()->setConfig(QStringLiteral("r2ai.async.purge"), previousAsyncPurge);
|
||||
asyncPurgeSaved = false;
|
||||
}
|
||||
|
||||
void R2AiDecompiler::clearCache()
|
||||
{
|
||||
for (RCodeMeta *code : std::as_const(cache)) {
|
||||
r_codemeta_free(code);
|
||||
}
|
||||
cache.clear();
|
||||
}
|
||||
|
||||
bool R2AiDecompiler::hasCachedResult(RVA addr) const
|
||||
{
|
||||
return cache.contains(cacheKeyForAddr(addr));
|
||||
}
|
||||
|
||||
RCodeMeta *R2AiDecompiler::decompileSync(RVA addr)
|
||||
{
|
||||
const QString key = cacheKeyForAddr(addr);
|
||||
if (RCodeMeta *cached = cache.value(key, nullptr)) {
|
||||
return copyCodeMeta(cached);
|
||||
}
|
||||
return Decompiler::makeWarning(tr("r2ai decompilation must be started explicitly"));
|
||||
}
|
||||
|
||||
void R2AiDecompiler::decompileAt(RVA addr)
|
||||
{
|
||||
RVA functionAddr = Core()->getFunctionStart(addr);
|
||||
if (functionAddr == RVA_INVALID) {
|
||||
functionAddr = addr;
|
||||
}
|
||||
|
||||
const QString key = cacheKeyForAddr(functionAddr);
|
||||
if (RCodeMeta *cached = cache.value(key, nullptr)) {
|
||||
emit finished(copyCodeMeta(cached));
|
||||
return;
|
||||
}
|
||||
|
||||
if (taskId > 0) {
|
||||
emit finished(Decompiler::makeWarning(tr("r2ai decompilation is already running")));
|
||||
return;
|
||||
}
|
||||
|
||||
ensureAsyncConfig();
|
||||
taskCacheKey = key;
|
||||
taskLastOutput.clear();
|
||||
|
||||
const QString result = Core()->cmd(decompileCommand(functionAddr));
|
||||
taskId = queuedTaskId(result);
|
||||
if (taskId <= 0) {
|
||||
restoreAsyncPurgeConfig();
|
||||
RCodeMeta *code = buildCodeMetaFromText(result);
|
||||
cache.insert(taskCacheKey, copyCodeMeta(code));
|
||||
taskCacheKey.clear();
|
||||
emit finished(code);
|
||||
return;
|
||||
}
|
||||
|
||||
pollTimer->start();
|
||||
}
|
||||
|
||||
void R2AiDecompiler::finishWithCode(RCodeMeta *code, bool storeInCache)
|
||||
{
|
||||
pollTimer->stop();
|
||||
if (storeInCache && code) {
|
||||
RCodeMeta *oldCode = cache.take(taskCacheKey);
|
||||
if (oldCode) {
|
||||
r_codemeta_free(oldCode);
|
||||
}
|
||||
cache.insert(taskCacheKey, copyCodeMeta(code));
|
||||
}
|
||||
taskId = 0;
|
||||
taskCacheKey.clear();
|
||||
taskLastOutput.clear();
|
||||
restoreAsyncPurgeConfig();
|
||||
emit finished(code);
|
||||
}
|
||||
|
||||
void R2AiDecompiler::finishWithWarning(const QString &message)
|
||||
{
|
||||
finishWithCode(Decompiler::makeWarning(message), false);
|
||||
}
|
||||
|
||||
void R2AiDecompiler::pollAsyncTask()
|
||||
{
|
||||
if (taskId <= 0) {
|
||||
pollTimer->stop();
|
||||
return;
|
||||
}
|
||||
|
||||
ensureAsyncConfig();
|
||||
const QJsonDocument document = parseJsonObjectResult(Core()->cmd(QStringLiteral("r2ai -sj")));
|
||||
if (!document.isObject()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QJsonArray tasks = document.object().value(QStringLiteral("tasks")).toArray();
|
||||
QJsonObject currentTask;
|
||||
for (const QJsonValue &value : tasks) {
|
||||
const QJsonObject task = value.toObject();
|
||||
if (task.value(QStringLiteral("id")).toInt() == taskId) {
|
||||
currentTask = task;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentTask.isEmpty()) {
|
||||
if (!taskLastOutput.isEmpty()) {
|
||||
finishWithCode(buildCodeMetaFromText(taskLastOutput), true);
|
||||
} else {
|
||||
finishWithWarning(tr("r2ai async task disappeared before producing output"));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const QString output = currentTask.value(QStringLiteral("output")).toString();
|
||||
if (!output.isEmpty()) {
|
||||
taskLastOutput = output;
|
||||
}
|
||||
|
||||
const QString state = currentTask.value(QStringLiteral("state")).toString();
|
||||
if (!isFinalState(state)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == QLatin1String("complete")) {
|
||||
if (taskLastOutput.isEmpty()) {
|
||||
finishWithWarning(tr("r2ai completed without output"));
|
||||
} else {
|
||||
finishWithCode(buildCodeMetaFromText(taskLastOutput), true);
|
||||
}
|
||||
} else if (state == QLatin1String("cancelled") || state == QLatin1String("stopped")) {
|
||||
finishWithWarning(tr("r2ai decompilation cancelled"));
|
||||
} else {
|
||||
const QString error = currentTask.value(QStringLiteral("error")).toString();
|
||||
finishWithWarning(error.isEmpty() ? tr("r2ai decompilation failed") : error);
|
||||
}
|
||||
}
|
||||
|
||||
void R2AiDecompiler::cancel()
|
||||
{
|
||||
if (taskId <= 0) {
|
||||
return;
|
||||
}
|
||||
Core()->cmd(QStringLiteral("r2ai -sk %1").arg(taskId));
|
||||
finishWithWarning(tr("r2ai decompilation cancelled"));
|
||||
}
|
||||
|
||||
void R2AiDecompiler::setOption(const QString &key, const QString &value)
|
||||
{
|
||||
Decompiler::setOption(key, value);
|
||||
clearCache();
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
#ifndef R2AI_DECOMPILER_H
|
||||
#define R2AI_DECOMPILER_H
|
||||
|
||||
#include "Decompiler.h"
|
||||
#include "IaitoCommon.h"
|
||||
|
||||
#include <QHash>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QTimer>
|
||||
|
||||
class R2AiDecompiler : public Decompiler
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
QTimer *pollTimer;
|
||||
QHash<QString, RCodeMeta *> cache;
|
||||
int taskId = 0;
|
||||
QString taskCacheKey;
|
||||
QString taskLastOutput;
|
||||
bool previousAsyncPurge = false;
|
||||
bool asyncPurgeSaved = false;
|
||||
mutable bool offsetCommandChecked = false;
|
||||
mutable bool offsetCommandAvailable = false;
|
||||
|
||||
QString cacheKeyForAddr(RVA addr) const;
|
||||
QString decompileCommand(RVA addr) const;
|
||||
void ensureAsyncConfig();
|
||||
void restoreAsyncPurgeConfig();
|
||||
void finishWithCode(RCodeMeta *code, bool storeInCache);
|
||||
void finishWithWarning(const QString &message);
|
||||
void clearCache();
|
||||
|
||||
private slots:
|
||||
void pollAsyncTask();
|
||||
|
||||
public:
|
||||
explicit R2AiDecompiler(QObject *parent = nullptr);
|
||||
~R2AiDecompiler() override;
|
||||
void decompileAt(RVA addr) override;
|
||||
RCodeMeta *decompileSync(RVA addr) override;
|
||||
void cancel() override;
|
||||
void setOption(const QString &key, const QString &value) override;
|
||||
|
||||
bool isRunning() override { return taskId > 0; }
|
||||
bool isCancelable() override { return taskId > 0; }
|
||||
bool prefersBackground() const override { return true; }
|
||||
bool requiresManualTrigger() const override { return true; }
|
||||
bool hasCachedResult(RVA addr) const override;
|
||||
QString getConfigPrefix() const override { return QStringLiteral("r2ai"); }
|
||||
|
||||
static bool isAvailable();
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "R2DecaiDecompiler.h"
|
||||
#include "Iaito.h"
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
|
||||
@@ -25,6 +26,71 @@ RCodeMeta *R2DecaiDecompiler::decompileSync(RVA addr)
|
||||
return code;
|
||||
}
|
||||
|
||||
static QStringList parseDecaiEvalLines(const QString &output)
|
||||
{
|
||||
static const QLatin1String kPrefix("decai -e ");
|
||||
QStringList result;
|
||||
for (const QString &raw : output.split(QLatin1Char('\n'))) {
|
||||
QString line = raw;
|
||||
if (line.endsWith(QLatin1Char('\r'))) {
|
||||
line.chop(1);
|
||||
}
|
||||
if (!line.startsWith(kPrefix)) {
|
||||
continue;
|
||||
}
|
||||
QString kv = line.mid(kPrefix.size());
|
||||
if (!kv.contains(QLatin1Char('='))) {
|
||||
continue;
|
||||
}
|
||||
result.append(kv);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QStringList R2DecaiDecompiler::listOptions()
|
||||
{
|
||||
// r2js console.log writes to stdout instead of the command's output buffer.
|
||||
const QByteArray script = R"((function() {
|
||||
var log = console.log;
|
||||
var lines = [];
|
||||
console.log = function() { lines.push(Array.prototype.join.call(arguments, " ")); };
|
||||
try {
|
||||
var output = r2.cmd("decai -e");
|
||||
if (output) { lines.push(output); }
|
||||
} finally {
|
||||
console.log = log;
|
||||
}
|
||||
r2.log(lines.join("\n"));
|
||||
})())";
|
||||
QString output = Core()->cmd(("js base64:" + script.toBase64()).constData());
|
||||
return parseDecaiEvalLines(output);
|
||||
}
|
||||
|
||||
QString R2DecaiDecompiler::getOption(const QString &key)
|
||||
{
|
||||
if (key.isEmpty()) {
|
||||
return QString();
|
||||
}
|
||||
for (const QString &kv : listOptions()) {
|
||||
int eq = kv.indexOf(QLatin1Char('='));
|
||||
if (eq < 0) {
|
||||
continue;
|
||||
}
|
||||
if (kv.left(eq) == key) {
|
||||
return kv.mid(eq + 1);
|
||||
}
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
void R2DecaiDecompiler::setOption(const QString &key, const QString &value)
|
||||
{
|
||||
if (key.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Core()->cmd(QStringLiteral("decai -e %1=%2").arg(key, value));
|
||||
}
|
||||
|
||||
void R2DecaiDecompiler::decompileAt(RVA addr)
|
||||
{
|
||||
if (task) {
|
||||
|
||||
@@ -21,6 +21,11 @@ public:
|
||||
RCodeMeta *decompileSync(RVA addr) override;
|
||||
|
||||
bool isRunning() override { return task != nullptr; }
|
||||
QString getConfigPrefix() const override { return QStringLiteral("decai"); }
|
||||
|
||||
QStringList listOptions() override;
|
||||
QString getOption(const QString &key) override;
|
||||
void setOption(const QString &key, const QString &value) override;
|
||||
|
||||
static bool isAvailable();
|
||||
};
|
||||
|
||||
@@ -2,110 +2,17 @@
|
||||
#include "R2GhidraCmdDecompiler.h"
|
||||
#include "Iaito.h"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
|
||||
/*
|
||||
RCodeMeta *Decompiler::makeWarning(QString warningMessage){
|
||||
std::string temporary = warningMessage.toStdString();
|
||||
return r_codemeta_new(temporary.c_str());
|
||||
}
|
||||
*/
|
||||
|
||||
R2GhidraCmdDecompiler::R2GhidraCmdDecompiler(QObject *parent)
|
||||
: Decompiler("pdg", "pdg", parent)
|
||||
{
|
||||
task = nullptr;
|
||||
}
|
||||
: R2JsonDecompiler(
|
||||
"pdg",
|
||||
"pdg",
|
||||
QStringLiteral("pdgj"),
|
||||
QStringLiteral("r2ghidra"),
|
||||
AnnotationMode::Full,
|
||||
parent)
|
||||
{}
|
||||
|
||||
bool R2GhidraCmdDecompiler::isAvailable()
|
||||
{
|
||||
return Core()->cmdList("e cmd.pdc=?").contains(QStringLiteral("pdg"));
|
||||
}
|
||||
|
||||
RCodeMeta *R2GhidraCmdDecompiler::decompileSync(RVA addr)
|
||||
{
|
||||
auto document = Core()->cmdj("pdgj @ " + QString::number(addr));
|
||||
QJsonObject json = document.object();
|
||||
if (json.isEmpty()) {
|
||||
// emit finished(Decompiler::makeWarning(tr("Failed to parse JSON
|
||||
// from pdc")));
|
||||
return NULL;
|
||||
}
|
||||
QString codeString = json["code"].toString();
|
||||
RCodeMeta *code = r_codemeta_new("");
|
||||
QJsonArray linesArray = json["annotations"].toArray();
|
||||
for (const QJsonValueRef line : linesArray) {
|
||||
QJsonObject lineObject = line.toObject();
|
||||
if (lineObject.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
if (lineObject["type"].toString() != "offset") {
|
||||
continue;
|
||||
}
|
||||
RCodeMetaItem *mi = r_codemeta_item_new();
|
||||
mi->start = lineObject["start"].toInt();
|
||||
mi->end = lineObject["end"].toInt();
|
||||
bool ok;
|
||||
mi->type = R_CODEMETA_TYPE_OFFSET;
|
||||
mi->offset.offset = lineObject["offset"].toVariant().toULongLong(&ok);
|
||||
r_codemeta_add_item(code, mi);
|
||||
}
|
||||
|
||||
for (const auto line : json["errors"].toArray()) {
|
||||
if (!line.isString()) {
|
||||
continue;
|
||||
}
|
||||
codeString.append(line.toString() + "\n");
|
||||
}
|
||||
std::string tmp = codeString.toStdString();
|
||||
code->code = strdup(tmp.c_str());
|
||||
return code;
|
||||
}
|
||||
|
||||
void R2GhidraCmdDecompiler::decompileAt(RVA addr)
|
||||
{
|
||||
if (task) {
|
||||
return;
|
||||
}
|
||||
task = new R2Task("pdgj @ " + QString::number(addr));
|
||||
connect(task, &R2Task::finished, this, [this]() {
|
||||
QJsonObject json = task->getResultJson().object();
|
||||
delete task;
|
||||
task = nullptr;
|
||||
if (json.isEmpty()) {
|
||||
emit finished(Decompiler::makeWarning(tr("Failed to parse JSON from r2ghidra")));
|
||||
return;
|
||||
}
|
||||
RCodeMeta *code = r_codemeta_new("");
|
||||
QString codeString = json["code"].toString();
|
||||
QJsonArray linesArray = json["annotations"].toArray();
|
||||
for (const QJsonValueRef line : linesArray) {
|
||||
QJsonObject lineObject = line.toObject();
|
||||
if (lineObject.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
if (lineObject["type"].toString() != "offset") {
|
||||
continue;
|
||||
}
|
||||
RCodeMetaItem *mi = r_codemeta_item_new();
|
||||
mi->start = lineObject["start"].toInt();
|
||||
mi->end = lineObject["end"].toInt();
|
||||
bool ok;
|
||||
mi->type = R_CODEMETA_TYPE_OFFSET;
|
||||
mi->offset.offset = lineObject["offset"].toVariant().toULongLong(&ok);
|
||||
r_codemeta_add_item(code, mi);
|
||||
}
|
||||
|
||||
for (const auto line : json["errors"].toArray()) {
|
||||
if (!line.isString()) {
|
||||
continue;
|
||||
}
|
||||
codeString.append(line.toString() + "\n");
|
||||
}
|
||||
std::string tmp = codeString.toStdString();
|
||||
code->code = strdup(tmp.c_str());
|
||||
emit finished(code);
|
||||
});
|
||||
task->startTask();
|
||||
}
|
||||
@@ -2,25 +2,15 @@
|
||||
#define R2G_DECOMPILER_H
|
||||
|
||||
#include "Decompiler.h"
|
||||
#include "IaitoCommon.h"
|
||||
#include "R2Task.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
class R2GhidraCmdDecompiler : public Decompiler
|
||||
class R2GhidraCmdDecompiler : public R2JsonDecompiler
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
R2Task *task;
|
||||
|
||||
public:
|
||||
explicit R2GhidraCmdDecompiler(QObject *parent = nullptr);
|
||||
RCodeMeta *decompileSync(RVA addr) override;
|
||||
void decompileAt(RVA addr) override;
|
||||
|
||||
bool isRunning() override { return task != nullptr; }
|
||||
QString getConfigPrefix() const override { return QStringLiteral("r2ghidra"); }
|
||||
|
||||
static bool isAvailable();
|
||||
};
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
|
||||
#include "R2HermesDecompiler.h"
|
||||
#include "Iaito.h"
|
||||
|
||||
R2HermesDecompiler::R2HermesDecompiler(QObject *parent)
|
||||
: R2JsonDecompiler(
|
||||
"r2hermes",
|
||||
"r2hermes",
|
||||
QStringLiteral("pd:hj"),
|
||||
QStringLiteral("r2hermes (pd:hj)"),
|
||||
AnnotationMode::OffsetsOnly,
|
||||
parent)
|
||||
{}
|
||||
|
||||
bool R2HermesDecompiler::isAvailable()
|
||||
{
|
||||
return Core()->cmd("Lc").contains(QStringLiteral("core_hbc"));
|
||||
}
|
||||