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 |
No files matched your search
@@ -1,12 +1,18 @@
|
||||
name: iaito CI
|
||||
|
||||
env:
|
||||
R2V: 6.1.4
|
||||
R2GHIDRA_VERSION: 6.1.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
|
||||
@@ -15,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@v6
|
||||
- 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@v6
|
||||
- uses: actions/setup-python@v7
|
||||
with:
|
||||
python-version: 3.12.x
|
||||
- name: apt dependencies
|
||||
@@ -35,37 +84,34 @@ 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@v6
|
||||
- uses: actions/setup-python@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
python-version: 3.12.x
|
||||
- name: Install indent-check 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
|
||||
- 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:
|
||||
runs-on: ubuntu-22.04
|
||||
needs: source
|
||||
env:
|
||||
R2V: ${{ needs.source.outputs.r2 }}
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
ref: ${{ needs.source.outputs.sha }}
|
||||
submodules: false # 'recursive' 'true' or 'false'
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@v6
|
||||
- uses: actions/setup-python@v7
|
||||
with:
|
||||
python-version: 3.12.x
|
||||
- name: apt dependencies
|
||||
@@ -82,6 +128,9 @@ jobs:
|
||||
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@v7
|
||||
@@ -89,13 +138,14 @@ jobs:
|
||||
name: iaito-amd64.deb
|
||||
path: dist/debian/*/*.deb
|
||||
acr-linux-nodebugger:
|
||||
runs-on: ubuntu-22.04
|
||||
if: ${{ github.event_name != 'workflow_dispatch' }}
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
submodules: false # 'recursive' 'true' or 'false'
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@v6
|
||||
- uses: actions/setup-python@v7
|
||||
with:
|
||||
python-version: 3.12.x
|
||||
- name: apt dependencies
|
||||
@@ -112,7 +162,15 @@ jobs:
|
||||
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:
|
||||
@@ -125,11 +183,12 @@ jobs:
|
||||
name: acr-macos-${{ matrix.arch }}
|
||||
runs-on: ${{ matrix.runs-on }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
ref: ${{ needs.source.outputs.sha }}
|
||||
submodules: false # 'recursive' 'true' or 'false'
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@v6
|
||||
- uses: actions/setup-python@v7
|
||||
with:
|
||||
python-version: 3.12.x
|
||||
- name: uname
|
||||
@@ -145,10 +204,41 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{github.token}}
|
||||
run: |
|
||||
curl -Lo radare2.pkg "https://github.com/radareorg/radare2/releases/download/${{env.R2V}}/radare2-${{matrix.arch}}-${{env.R2V}}.pkg"
|
||||
curl -Lo r2ai.tar.gz "$(gh api /repos/radareorg/r2ai/releases/latest --jq .tarball_url)"
|
||||
curl -Lo r2ghidra.zip "https://github.com/radareorg/r2ghidra/releases/download/${{env.R2GHIDRA_VERSION}}/r2ghidra-${{env.R2GHIDRA_VERSION}}-macos-${{matrix.arch_alt||matrix.arch}}.zip"
|
||||
curl -Lo r2ghidra_sleigh.zip "https://github.com/radareorg/r2ghidra/releases/download/${{env.R2GHIDRA_VERSION}}/r2ghidra_sleigh-${{env.R2GHIDRA_VERSION}}.zip"
|
||||
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 /
|
||||
@@ -165,59 +255,44 @@ jobs:
|
||||
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@v6
|
||||
- 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@v6
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: install dependencies
|
||||
if: contains(matrix.os, 'macos')
|
||||
run: pip3 install meson ninja
|
||||
- name: install Qt
|
||||
if: contains(matrix.os, 'macos')
|
||||
uses: jurplel/install-qt-action@v4
|
||||
with:
|
||||
version: ${{env.QT_MAC_INSTALL_VERSION}}
|
||||
- name: macos build
|
||||
if: contains(matrix.os, 'macos')
|
||||
run: |
|
||||
meson setup -Dwith_qt6=true build src
|
||||
ninja -C build -j4
|
||||
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: 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@v6
|
||||
- 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
|
||||
@@ -233,6 +308,8 @@ jobs:
|
||||
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
|
||||
@@ -255,33 +332,46 @@ jobs:
|
||||
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@v6
|
||||
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@v6
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
ref: refs/tags/${{ needs.check_release.outputs.tag_name }}
|
||||
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v8
|
||||
@@ -364,23 +454,34 @@ jobs:
|
||||
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@v6
|
||||
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@v8
|
||||
with:
|
||||
@@ -396,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:
|
||||
@@ -445,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@v6
|
||||
- uses: actions/checkout@v7
|
||||
- run: semgrep ci
|
||||
@@ -3,6 +3,9 @@ AStyle*
|
||||
./config.mk
|
||||
config.mk
|
||||
|
||||
# Generated configuration header (qmake / Meson)
|
||||
IaitoConfig.h
|
||||
|
||||
# OSX files
|
||||
*.DS_Store
|
||||
.AppleDouble
|
||||
@@ -22,6 +25,7 @@ config.mk
|
||||
# Qt files
|
||||
*.qmake.cache
|
||||
*.qmake.stash
|
||||
src/Makefile
|
||||
*.pro.user
|
||||
*.pro.user.*
|
||||
*.qbs.user
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
include config.mk
|
||||
-include config.mk
|
||||
|
||||
ifeq ($(shell uname),Darwin)
|
||||
BIN=build/iaito.app/Contents/MacOS/iaito
|
||||
@@ -71,6 +71,7 @@ mrproper: clean
|
||||
|
||||
.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),)
|
||||
@@ -97,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
|
||||
|
||||
@@ -109,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)
|
||||
@@ -119,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)
|
||||
|
||||
|
||||
@@ -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,5 +1,5 @@
|
||||
#!/bin/sh
|
||||
# This script was automatically generated by ACR v2.2.6
|
||||
# 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
|
||||
@@ -129,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.1.4' ; VERSION_MAJOR=6; VERSION_MINOR=1; VERSION_PATCH=4; VERSION_NUMBER=60104; 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.1.4 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]...
|
||||
|
||||
@@ -212,10 +212,10 @@ ocho() {
|
||||
|
||||
show_version() {
|
||||
if [ "$QUIET" = 1 ]; then
|
||||
echo "6.1.4"
|
||||
echo "6.2.6"
|
||||
exit 0
|
||||
fi
|
||||
echo "iaito-6.1.4 configuration script done with acr v2.2.6.
|
||||
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
|
||||
@@ -244,7 +244,7 @@ case $flag in
|
||||
show_version ; ;;
|
||||
-r|--r|--report)
|
||||
echo "PKGNAME: iaito"
|
||||
echo "VERSION: 6.1.4"
|
||||
echo "VERSION: 6.2.6"
|
||||
echo "AUTHOR: pancake"
|
||||
echo "EMAIL: [email protected]"
|
||||
echo "LANGS: c++"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
PKGNAME iaito
|
||||
VERSION 6.1.4
|
||||
VERSION 6.2.6
|
||||
CONTACT pancake ; [email protected]
|
||||
|
||||
LANG_CXX!
|
||||
|
||||
@@ -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
|
||||
@@ -3,8 +3,8 @@ TEMPLATE = app
|
||||
TARGET = iaito
|
||||
|
||||
IAITO_VERSION_MAJOR = 6
|
||||
IAITO_VERSION_MINOR = 1
|
||||
IAITO_VERSION_PATCH = 4
|
||||
IAITO_VERSION_MINOR = 2
|
||||
IAITO_VERSION_PATCH = 6
|
||||
|
||||
CONFIG += c++20
|
||||
QMAKE_CXXFLAGS += -std=c++20
|
||||
@@ -164,6 +164,8 @@ IAITO_R2GHIDRA_STATIC {
|
||||
QMAKE_SUBSTITUTES += IaitoConfig.h.in
|
||||
|
||||
SOURCES += \
|
||||
common/ExceptionInfo.cpp \
|
||||
widgets/ExceptionBar.cpp \
|
||||
Main.cpp \
|
||||
core/Iaito.cpp \
|
||||
dialogs/EditStringDialog.cpp \
|
||||
@@ -171,9 +173,11 @@ SOURCES += \
|
||||
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 \
|
||||
@@ -181,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 \
|
||||
@@ -199,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 \
|
||||
@@ -207,32 +215,43 @@ 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/DeepLink.cpp \
|
||||
common/SamplesDB.cpp \
|
||||
dialogs/R2PluginsDialog.cpp \
|
||||
widgets/IaitoDockWidget.cpp \
|
||||
widgets/IaitoTreeWidget.cpp \
|
||||
@@ -240,6 +259,7 @@ SOURCES += \
|
||||
widgets/BinariesWidget.cpp \
|
||||
widgets/FilesWidget.cpp \
|
||||
widgets/FilesystemWidget.cpp \
|
||||
common/SourceLineReference.cpp \
|
||||
widgets/GraphWidget.cpp \
|
||||
widgets/OverviewWidget.cpp \
|
||||
common/JsonTreeItem.cpp \
|
||||
@@ -261,9 +281,10 @@ SOURCES += \
|
||||
dialogs/R2TaskDialog.cpp \
|
||||
widgets/DebugActions.cpp \
|
||||
widgets/MemoryMapWidget.cpp \
|
||||
dialogs/preferences/DebugOptionsWidget.cpp \
|
||||
dialogs/preferences/WebserverOptionsWidget.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 \
|
||||
@@ -271,6 +292,7 @@ SOURCES += \
|
||||
dialogs/SetToDataDialog.cpp \
|
||||
dialogs/EditVariablesDialog.cpp \
|
||||
dialogs/EditFunctionDialog.cpp \
|
||||
dialogs/SwitchJumpTableDialog.cpp \
|
||||
widgets/IaitoTreeView.cpp \
|
||||
widgets/ComboQuickFilterView.cpp \
|
||||
dialogs/HexdumpRangeDialog.cpp \
|
||||
@@ -289,25 +311,36 @@ SOURCES += \
|
||||
common/ColorThemeWorker.cpp \
|
||||
widgets/ColorThemeComboBox.cpp \
|
||||
widgets/ColorThemeListView.cpp \
|
||||
dialogs/preferences/ColorThemeEditDialog.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 \
|
||||
@@ -320,19 +353,28 @@ 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/AssemblerDialog.cpp \
|
||||
dialogs/ScriptManagerDialog.cpp \
|
||||
dialogs/ScriptManagerWidget.cpp \
|
||||
widgets/ZoomWidget.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 \
|
||||
@@ -343,19 +385,23 @@ HEADERS += \
|
||||
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 \
|
||||
@@ -363,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 \
|
||||
@@ -373,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 \
|
||||
@@ -381,31 +431,43 @@ 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 \
|
||||
dialogs/R2PluginsDialog.h \
|
||||
widgets/IaitoDockWidget.h \
|
||||
@@ -414,6 +476,7 @@ HEADERS += \
|
||||
widgets/BinariesWidget.h \
|
||||
widgets/FilesWidget.h \
|
||||
widgets/FilesystemWidget.h \
|
||||
common/SourceLineReference.h \
|
||||
widgets/GraphWidget.h \
|
||||
widgets/OverviewWidget.h \
|
||||
common/JsonTreeItem.h \
|
||||
@@ -439,9 +502,10 @@ HEADERS += \
|
||||
dialogs/R2TaskDialog.h \
|
||||
widgets/DebugActions.h \
|
||||
widgets/MemoryMapWidget.h \
|
||||
dialogs/preferences/DebugOptionsWidget.h \
|
||||
dialogs/preferences/WebserverOptionsWidget.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 \
|
||||
@@ -450,6 +514,7 @@ HEADERS += \
|
||||
common/InitialOptions.h \
|
||||
dialogs/EditVariablesDialog.h \
|
||||
dialogs/EditFunctionDialog.h \
|
||||
dialogs/SwitchJumpTableDialog.h \
|
||||
widgets/IaitoTreeView.h \
|
||||
widgets/ComboQuickFilterView.h \
|
||||
dialogs/HexdumpRangeDialog.h \
|
||||
@@ -470,24 +535,35 @@ HEADERS += \
|
||||
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 \
|
||||
@@ -503,25 +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 \
|
||||
common/CodeMetaRange.h \
|
||||
dialogs/PackageManagerDialog.h \
|
||||
dialogs/AssemblerDialog.h \
|
||||
dialogs/ScriptManagerDialog.h \
|
||||
dialogs/ScriptManagerWidget.h \
|
||||
widgets/ZoomWidget.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 \
|
||||
@@ -538,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 \
|
||||
@@ -561,8 +646,9 @@ FORMS += \
|
||||
widgets/ProcessesWidget.ui \
|
||||
widgets/BacktraceWidget.ui \
|
||||
dialogs/MapFileDialog.ui \
|
||||
dialogs/preferences/DebugOptionsWidget.ui \
|
||||
dialogs/preferences/WebserverOptionsWidget.ui \
|
||||
dialogs/settings/DebugOptionsWidget.ui \
|
||||
dialogs/settings/WebserverOptionsWidget.ui \
|
||||
dialogs/settings/SamplesOptionsWidget.ui \
|
||||
widgets/BreakpointWidget.ui \
|
||||
dialogs/BreakpointsDialog.ui \
|
||||
dialogs/AttachProcDialog.ui \
|
||||
@@ -570,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 \
|
||||
@@ -578,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,13 +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/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>
|
||||
@@ -16,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
|
||||
@@ -27,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>
|
||||
|
||||
@@ -59,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
|
||||
@@ -72,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
|
||||
@@ -106,6 +148,20 @@ IaitoApplication::IaitoApplication(int &argc, char **argv)
|
||||
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)
|
||||
@@ -116,8 +172,7 @@ IaitoApplication::IaitoApplication(int &argc, char **argv)
|
||||
#endif
|
||||
#endif
|
||||
if (!parseCommandLineOptions()) {
|
||||
QCoreApplication::exit();
|
||||
// std::exit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!versionCheck()) {
|
||||
@@ -153,6 +208,10 @@ 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")));
|
||||
@@ -181,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()));
|
||||
@@ -216,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();
|
||||
@@ -268,8 +337,11 @@ IaitoApplication::IaitoApplication(int &argc, char **argv)
|
||||
|
||||
IaitoApplication::~IaitoApplication()
|
||||
{
|
||||
Plugins()->destroyPlugins();
|
||||
delete mainWindow;
|
||||
if (mainWindow) {
|
||||
SamplesDB::waitForPending();
|
||||
Plugins()->destroyPlugins();
|
||||
delete mainWindow;
|
||||
}
|
||||
}
|
||||
|
||||
void IaitoApplication::launchNewInstance(const QStringList &args)
|
||||
@@ -296,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());
|
||||
@@ -389,6 +466,10 @@ 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(
|
||||
@@ -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);
|
||||
|
||||
@@ -451,11 +536,26 @@ bool IaitoApplication::parseCommandLineOptions()
|
||||
cmd_parser.addOption(debugOption);
|
||||
#endif
|
||||
|
||||
cmd_parser.process(*this);
|
||||
// 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);
|
||||
@@ -505,6 +605,13 @@ bool IaitoApplication::parseCommandLineOptions()
|
||||
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) {
|
||||
case AutomaticAnalysisLevel::Ask:
|
||||
break;
|
||||
@@ -563,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"
|
||||
|
||||
@@ -19,6 +18,7 @@ struct IaitoCommandLineOptions
|
||||
bool enableIaitoPlugins = true;
|
||||
bool enableR2Plugins = true;
|
||||
bool showWelcomeDialog = false;
|
||||
QString deepLinkUrl;
|
||||
};
|
||||
|
||||
class IaitoApplication : public QApplication
|
||||
@@ -30,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:
|
||||
/**
|
||||
@@ -50,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
|
||||
@@ -5,9 +5,75 @@
|
||||
#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.
|
||||
*
|
||||
@@ -52,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]);
|
||||
@@ -84,6 +154,9 @@ int main(int argc, char *argv[])
|
||||
#endif
|
||||
|
||||
IaitoApplication a(argc, argv);
|
||||
if (!a.getMainWindow()) {
|
||||
return a.getExitCode();
|
||||
}
|
||||
|
||||
return a.exec();
|
||||
}
|
||||
|
||||
@@ -12,17 +12,20 @@ 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 *)
|
||||
@@ -32,6 +35,45 @@ void AddressableFilterProxyModel::setSourceModel(QAbstractItemModel *)
|
||||
|
||||
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
|
||||
@@ -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)
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
#define CODEMETA_RANGE_H
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QJsonValue>
|
||||
#include <QString>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
@@ -31,6 +33,61 @@ inline std::optional<CodeMetaRange> codeMetaRangeFromJson(const QJsonObject &obj
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -24,13 +24,28 @@ static QJsonArray variantToRgba(const QVariant &v)
|
||||
} // 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",
|
||||
"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",
|
||||
= {"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
|
||||
|
||||
@@ -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
|
||||
@@ -1,12 +1,20 @@
|
||||
#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
|
||||
@@ -20,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>
|
||||
@@ -36,8 +46,12 @@ static inline int variantTypeId(const QVariant &v)
|
||||
|
||||
constexpr int VisualNavbarThicknessDefault = 15;
|
||||
constexpr int VisualNavbarThicknessMin = 8;
|
||||
constexpr int VisualNavbarThicknessMax = 64;
|
||||
constexpr bool VisualNavbarUseThemeColorsDefault = false;
|
||||
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
|
||||
@@ -95,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",
|
||||
@@ -117,7 +133,7 @@ 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",
|
||||
@@ -147,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},
|
||||
@@ -166,14 +182,16 @@ static const QHash<QString, QVariant> asmOptions
|
||||
{"asm.refptr", false},
|
||||
{"asm.flags.real", true},
|
||||
{"esil.breakoninvalid", false},
|
||||
{"graph.addr", true},
|
||||
{"graph.addr", false},
|
||||
{"asm.addr", true},
|
||||
{"asm.addr.relto", ""},
|
||||
{"asm.flags.addr", false}};
|
||||
{"asm.addr.base", "16"},
|
||||
{"asm.flags.offset", false}};
|
||||
|
||||
Configuration::Configuration()
|
||||
: QObject()
|
||||
, nativePalette(qApp->palette())
|
||||
, defaultAppFont(qApp->font())
|
||||
{
|
||||
mPtr = this;
|
||||
if (!s.isWritable()) {
|
||||
@@ -289,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
|
||||
@@ -384,111 +412,195 @@ bool Configuration::nativeWindowIsDark()
|
||||
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 if (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 if (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 if (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 if (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", 13)).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() const
|
||||
const QFont Configuration::getSmallFont(bool zoomed) const
|
||||
{
|
||||
if (IaitoStyle::instance() && IaitoStyle::instance()->theme().fonts.custom) {
|
||||
return zoomed ? getFont() : getBaseFont();
|
||||
}
|
||||
QFont font = getBaseFont();
|
||||
font.setPointSizeF(qMax(font.pointSizeF() * getZoomFactor() * 0.85, 6.0));
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -518,7 +630,7 @@ void Configuration::setZoomFactor(qreal zoom)
|
||||
Configuration::VisualNavbarLocation Configuration::getVisualNavbarLocation() const
|
||||
{
|
||||
auto location = static_cast<VisualNavbarLocation>(
|
||||
s.value("visualNavbarLocation", static_cast<int>(VisualNavbarLocation::Top)).toInt());
|
||||
s.value("visualNavbarLocation", static_cast<int>(VisualNavbarLocation::Left)).toInt());
|
||||
switch (location) {
|
||||
case VisualNavbarLocation::Top:
|
||||
case VisualNavbarLocation::Bottom:
|
||||
@@ -528,7 +640,7 @@ Configuration::VisualNavbarLocation Configuration::getVisualNavbarLocation() con
|
||||
case VisualNavbarLocation::SuperBottom:
|
||||
return location;
|
||||
}
|
||||
return VisualNavbarLocation::Top;
|
||||
return VisualNavbarLocation::Left;
|
||||
}
|
||||
|
||||
void Configuration::setVisualNavbarLocation(VisualNavbarLocation location)
|
||||
@@ -557,19 +669,43 @@ void Configuration::setVisualNavbarThickness(int thickness)
|
||||
emit visualNavbarThicknessChanged(thickness);
|
||||
}
|
||||
|
||||
bool Configuration::getVisualNavbarUseThemeColors() const
|
||||
int Configuration::getVisualNavbarResolution() const
|
||||
{
|
||||
return s.value("visualNavbarUseThemeColors", VisualNavbarUseThemeColorsDefault).toBool();
|
||||
return qBound(1, s.value("visualNavbarResolution", 1).toInt(), VisualNavbarResolutionMax);
|
||||
}
|
||||
|
||||
void Configuration::setVisualNavbarUseThemeColors(bool enabled)
|
||||
void Configuration::setVisualNavbarResolution(int divisor)
|
||||
{
|
||||
if (getVisualNavbarUseThemeColors() == enabled) {
|
||||
divisor = qBound(1, divisor, VisualNavbarResolutionMax);
|
||||
if (getVisualNavbarResolution() == divisor) {
|
||||
return;
|
||||
}
|
||||
s.setValue("visualNavbarResolution", divisor);
|
||||
emit visualNavbarResolutionChanged(divisor);
|
||||
}
|
||||
|
||||
s.setValue("visualNavbarUseThemeColors", enabled);
|
||||
emit colorsUpdated();
|
||||
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
|
||||
@@ -587,16 +723,24 @@ 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++) {
|
||||
@@ -695,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 {
|
||||
@@ -751,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()
|
||||
@@ -765,16 +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()
|
||||
{
|
||||
// Cached once, but the Native entry's darkness flag is refreshed on
|
||||
// every access so that live OS theme changes are reflected immediately.
|
||||
static QList<IaitoInterfaceTheme> list
|
||||
static QList<IaitoInterfaceTheme> list;
|
||||
list
|
||||
= {{"Native", Configuration::nativeWindowIsDark() ? DarkFlag : LightFlag},
|
||||
{"Dark", DarkFlag},
|
||||
{"Midnight", DarkFlag},
|
||||
{"Light", LightFlag}};
|
||||
list[0].flag = Configuration::nativeWindowIsDark() ? DarkFlag : 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;
|
||||
}
|
||||
|
||||
@@ -915,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()
|
||||
@@ -981,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,
|
||||
@@ -35,10 +36,14 @@ class IAITO_EXPORT Configuration : public QObject
|
||||
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
|
||||
@@ -56,10 +61,8 @@ private:
|
||||
// Colors
|
||||
void loadBaseThemeNative();
|
||||
void loadBaseThemeDark();
|
||||
void loadNativeStylesheet();
|
||||
void loadLightStylesheet();
|
||||
void loadDarkStylesheet();
|
||||
void loadMidnightStylesheet();
|
||||
Theme nativeEngineTheme();
|
||||
bool loadUserStylesheet(const QString &name);
|
||||
|
||||
void onSystemColorSchemeChanged();
|
||||
|
||||
@@ -71,6 +74,17 @@ protected:
|
||||
|
||||
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;
|
||||
|
||||
@@ -106,8 +120,9 @@ public:
|
||||
* @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() const;
|
||||
const QFont getSmallFont(bool zoomed = true) const;
|
||||
void setFont(const QFont &font);
|
||||
qreal getZoomFactor() const;
|
||||
void setZoomFactor(qreal zoom);
|
||||
@@ -115,8 +130,10 @@ public:
|
||||
void setVisualNavbarLocation(VisualNavbarLocation location);
|
||||
int getVisualNavbarThickness() const;
|
||||
void setVisualNavbarThickness(int thickness);
|
||||
bool getVisualNavbarUseThemeColors() const;
|
||||
void setVisualNavbarUseThemeColors(bool enabled);
|
||||
int getVisualNavbarResolution() const;
|
||||
void setVisualNavbarResolution(int divisor);
|
||||
MemoryScrollBarMode getMemoryScrollBarMode() const;
|
||||
void setMemoryScrollBarMode(MemoryScrollBarMode mode);
|
||||
|
||||
// Colors
|
||||
bool windowColorIsDark();
|
||||
@@ -146,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();
|
||||
|
||||
@@ -154,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);
|
||||
@@ -183,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)
|
||||
@@ -196,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(); }
|
||||
@@ -231,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
|
||||
@@ -254,6 +299,9 @@ signals:
|
||||
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
|
||||
|
||||
@@ -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,13 +4,16 @@
|
||||
|
||||
#include <QDebug>
|
||||
#include <QMetaObject>
|
||||
#include <QThread>
|
||||
|
||||
DecompileTask::DecompileTask(Decompiler *decompiler, RVA addr, QObject *parent)
|
||||
: AsyncTask()
|
||||
, decompiler(decompiler)
|
||||
, addr(addr)
|
||||
, code(nullptr)
|
||||
{}
|
||||
{
|
||||
setParent(parent);
|
||||
}
|
||||
|
||||
DecompileTask::~DecompileTask() {}
|
||||
|
||||
@@ -18,6 +21,14 @@ void DecompileTask::interrupt()
|
||||
{
|
||||
loopInterrupted = true;
|
||||
AsyncTask::interrupt();
|
||||
if (decompiler) {
|
||||
if (decompiler->thread() == QThread::currentThread()) {
|
||||
decompiler->cancel();
|
||||
} else {
|
||||
QMetaObject::invokeMethod(
|
||||
decompiler, [this]() { decompiler->cancel(); }, Qt::QueuedConnection);
|
||||
}
|
||||
}
|
||||
Core()->setConsBreaked();
|
||||
// Previously we raised SIGINT in-process here which can interrupt
|
||||
// Qt/libc allocations and lead to crashes. Instead rely on radare2's
|
||||
@@ -35,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
|
||||
@@ -97,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
|
||||
|
||||
@@ -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,8 +1,14 @@
|
||||
|
||||
#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)
|
||||
@@ -18,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
|
||||
@@ -44,6 +101,40 @@ 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;
|
||||
}
|
||||
@@ -67,15 +158,11 @@ void DecompilerHighlighter::highlightBlock(const QString &)
|
||||
if (!range) {
|
||||
return;
|
||||
}
|
||||
// QSyntaxHighlighter::setFormat takes positions relative to the
|
||||
// current block, but intersectCodeMetaRange returns absolute
|
||||
// document offsets — rebase by subtracting the block start.
|
||||
const size_t relStart = range->start - start;
|
||||
if (relStart > static_cast<size_t>(std::numeric_limits<int>::max())
|
||||
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>(relStart), static_cast<int>(range->length), format[type]);
|
||||
setFormat(static_cast<int>(range->start), static_cast<int>(range->length), format[type]);
|
||||
};
|
||||
#if R2_ABIVERSION >= 40
|
||||
std::unique_ptr<RVecCodeMetaItemPtr, decltype(&RVecCodeMetaItemPtr_free)>
|
||||
@@ -93,4 +180,5 @@ void DecompilerHighlighter::highlightBlock(const QString &)
|
||||
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;
|
||||
@@ -316,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
|
||||
@@ -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,7 +30,7 @@ struct InitialOptions
|
||||
QString os;
|
||||
bool analVars = true;
|
||||
|
||||
Endianness endian;
|
||||
Endianness endian = Endianness::Auto;
|
||||
|
||||
bool writeEnabled = false;
|
||||
bool loadBinInfo = 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
|
||||
@@ -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
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
@@ -1,161 +1,18 @@
|
||||
|
||||
#include "R2GhidraCmdDecompiler.h"
|
||||
#include "CodeMetaRange.h"
|
||||
#include "Iaito.h"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
|
||||
static RSyntaxHighlightType syntaxHighlightTypeFromString(const QString &str)
|
||||
{
|
||||
if (str == "keyword") {
|
||||
return R_SYNTAX_HIGHLIGHT_TYPE_KEYWORD;
|
||||
} else if (str == "comment") {
|
||||
return R_SYNTAX_HIGHLIGHT_TYPE_COMMENT;
|
||||
} else if (str == "datatype") {
|
||||
return R_SYNTAX_HIGHLIGHT_TYPE_DATATYPE;
|
||||
} else if (str == "function_name") {
|
||||
return R_SYNTAX_HIGHLIGHT_TYPE_FUNCTION_NAME;
|
||||
} else if (str == "function_parameter") {
|
||||
return R_SYNTAX_HIGHLIGHT_TYPE_FUNCTION_PARAMETER;
|
||||
} else if (str == "local_variable") {
|
||||
return R_SYNTAX_HIGHLIGHT_TYPE_LOCAL_VARIABLE;
|
||||
} else if (str == "constant_variable") {
|
||||
return R_SYNTAX_HIGHLIGHT_TYPE_CONSTANT_VARIABLE;
|
||||
} else if (str == "global_variable") {
|
||||
return R_SYNTAX_HIGHLIGHT_TYPE_GLOBAL_VARIABLE;
|
||||
}
|
||||
return R_SYNTAX_HIGHLIGHT_TYPE_KEYWORD;
|
||||
}
|
||||
|
||||
static void parseCodeMetaJson(RCodeMeta *code, const QJsonArray &annotations, int codeLength)
|
||||
{
|
||||
for (const auto &line : annotations) {
|
||||
QJsonObject obj = line.toObject();
|
||||
if (obj.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
QString type = obj["type"].toString();
|
||||
auto range = codeMetaRangeFromJson(obj, static_cast<size_t>(codeLength));
|
||||
if (!range) {
|
||||
continue;
|
||||
}
|
||||
RCodeMetaItem *mi = r_codemeta_item_new();
|
||||
mi->start = range->start;
|
||||
mi->end = range->end;
|
||||
|
||||
if (type == "offset") {
|
||||
mi->type = R_CODEMETA_TYPE_OFFSET;
|
||||
bool ok = false;
|
||||
mi->offset.offset = obj["offset"].toVariant().toULongLong(&ok);
|
||||
if (!ok) {
|
||||
r_codemeta_item_free(mi);
|
||||
continue;
|
||||
}
|
||||
} else if (type == "syntax_highlight") {
|
||||
mi->type = R_CODEMETA_TYPE_SYNTAX_HIGHLIGHT;
|
||||
mi->syntax_highlight.type = syntaxHighlightTypeFromString(
|
||||
obj["syntax_highlight"].toString());
|
||||
} else if (type == "function_name") {
|
||||
mi->type = R_CODEMETA_TYPE_FUNCTION_NAME;
|
||||
mi->reference.name = strdup(obj["name"].toString().toUtf8().constData());
|
||||
bool ok = false;
|
||||
mi->reference.offset = obj["offset"].toVariant().toULongLong(&ok);
|
||||
if (!ok) {
|
||||
free(mi->reference.name);
|
||||
r_codemeta_item_free(mi);
|
||||
continue;
|
||||
}
|
||||
} else if (type == "global_variable") {
|
||||
mi->type = R_CODEMETA_TYPE_GLOBAL_VARIABLE;
|
||||
bool ok = false;
|
||||
mi->reference.offset = obj["offset"].toVariant().toULongLong(&ok);
|
||||
if (!ok) {
|
||||
r_codemeta_item_free(mi);
|
||||
continue;
|
||||
}
|
||||
} else if (type == "constant_variable") {
|
||||
mi->type = R_CODEMETA_TYPE_CONSTANT_VARIABLE;
|
||||
bool ok = false;
|
||||
mi->reference.offset = obj["offset"].toVariant().toULongLong(&ok);
|
||||
if (!ok) {
|
||||
r_codemeta_item_free(mi);
|
||||
continue;
|
||||
}
|
||||
} else if (type == "local_variable") {
|
||||
mi->type = R_CODEMETA_TYPE_LOCAL_VARIABLE;
|
||||
mi->variable.name = strdup(obj["name"].toString().toUtf8().constData());
|
||||
} else if (type == "function_parameter") {
|
||||
mi->type = R_CODEMETA_TYPE_FUNCTION_PARAMETER;
|
||||
mi->variable.name = strdup(obj["name"].toString().toUtf8().constData());
|
||||
} else {
|
||||
r_codemeta_item_free(mi);
|
||||
continue;
|
||||
}
|
||||
r_codemeta_add_item(code, mi);
|
||||
}
|
||||
}
|
||||
|
||||
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()) {
|
||||
return NULL;
|
||||
}
|
||||
QString codeString = json["code"].toString();
|
||||
RCodeMeta *code = r_codemeta_new("");
|
||||
parseCodeMetaJson(code, json["annotations"].toArray(), codeString.size());
|
||||
|
||||
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;
|
||||
}
|
||||
QString codeString = json["code"].toString();
|
||||
RCodeMeta *code = r_codemeta_new("");
|
||||
parseCodeMetaJson(code, json["annotations"].toArray(), codeString.size());
|
||||
|
||||
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"));
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#ifndef R2HERMES_DECOMPILER_H
|
||||
#define R2HERMES_DECOMPILER_H
|
||||
|
||||
#include "Decompiler.h"
|
||||
|
||||
class R2HermesDecompiler : public R2JsonDecompiler
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit R2HermesDecompiler(QObject *parent = nullptr);
|
||||
|
||||
QString getConfigPrefix() const override { return QStringLiteral("hbc"); }
|
||||
|
||||
static bool isAvailable();
|
||||
};
|
||||
|
||||
#endif // R2HERMES_DECOMPILER_H
|
||||
@@ -0,0 +1,90 @@
|
||||
#include "R2JadxDecompiler.h"
|
||||
#include "CodeMetaRange.h"
|
||||
#include "Iaito.h"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
|
||||
static void appendLine(RCodeMeta *code, QString &codeString, const QJsonObject &lineObject)
|
||||
{
|
||||
auto offset = codeMetaAddressFromJson(lineObject["offset"]);
|
||||
size_t start = codeString.length();
|
||||
codeString.append(lineObject["str"].toString() + "\n");
|
||||
if (!offset) {
|
||||
return;
|
||||
}
|
||||
|
||||
RCodeMetaItem *mi = r_codemeta_item_new();
|
||||
mi->start = start;
|
||||
mi->end = codeString.length();
|
||||
mi->type = R_CODEMETA_TYPE_OFFSET;
|
||||
mi->offset.offset = *offset;
|
||||
r_codemeta_add_item(code, mi);
|
||||
}
|
||||
|
||||
static RCodeMeta *buildCodeMeta(const QJsonObject &json)
|
||||
{
|
||||
RCodeMeta *code = r_codemeta_new("");
|
||||
QString codeString;
|
||||
|
||||
for (const auto line : json["lines"].toArray()) {
|
||||
QJsonObject lineObject = line.toObject();
|
||||
if (lineObject.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
appendLine(code, codeString, lineObject);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
R2JadxDecompiler::R2JadxDecompiler(QObject *parent)
|
||||
: Decompiler("r2jadx", "r2jadx", parent)
|
||||
{
|
||||
task = nullptr;
|
||||
}
|
||||
|
||||
bool R2JadxDecompiler::isAvailable()
|
||||
{
|
||||
const QStringList decompilers = Core()->cmdList("e cmd.pdc=?");
|
||||
return decompilers.contains(QStringLiteral("r2jadx"))
|
||||
|| decompilers.contains(QStringLiteral("pd:j"));
|
||||
}
|
||||
|
||||
RCodeMeta *R2JadxDecompiler::decompileSync(RVA addr)
|
||||
{
|
||||
auto document = Core()->cmdj("pd:jj @ " + QString::number(addr));
|
||||
QJsonObject json = document.object();
|
||||
if (json.isEmpty()) {
|
||||
return nullptr;
|
||||
}
|
||||
return buildCodeMeta(json);
|
||||
}
|
||||
|
||||
void R2JadxDecompiler::decompileAt(RVA addr)
|
||||
{
|
||||
if (task) {
|
||||
return;
|
||||
}
|
||||
task = new R2Task("pd:jj @ " + 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 r2jadx (pd:jj)")));
|
||||
return;
|
||||
}
|
||||
emit finished(buildCodeMeta(json));
|
||||
});
|
||||
task->startTask();
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#ifndef R2JADX_DECOMPILER_H
|
||||
#define R2JADX_DECOMPILER_H
|
||||
|
||||
#include "Decompiler.h"
|
||||
#include "IaitoCommon.h"
|
||||
#include "R2Task.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
class R2JadxDecompiler : public Decompiler
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
R2Task *task;
|
||||
|
||||
public:
|
||||
explicit R2JadxDecompiler(QObject *parent = nullptr);
|
||||
RCodeMeta *decompileSync(RVA addr) override;
|
||||
void decompileAt(RVA addr) override;
|
||||
|
||||
bool isRunning() override { return task != nullptr; }
|
||||
|
||||
static bool isAvailable();
|
||||
};
|
||||
|
||||
#endif // R2JADX_DECOMPILER_H
|
||||
@@ -0,0 +1,17 @@
|
||||
#include "R2SourceDecompiler.h"
|
||||
#include "Iaito.h"
|
||||
|
||||
R2SourceDecompiler::R2SourceDecompiler(QObject *parent)
|
||||
: R2JsonDecompiler(
|
||||
QStringLiteral("source"),
|
||||
QStringLiteral("source"),
|
||||
QStringLiteral("CLdj"),
|
||||
QStringLiteral("CLd"),
|
||||
AnnotationMode::OffsetsOnly,
|
||||
parent)
|
||||
{}
|
||||
|
||||
bool R2SourceDecompiler::isAvailable()
|
||||
{
|
||||
return Core()->cmd(QStringLiteral("CLd?")).contains(QStringLiteral("CLdj"));
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
#ifndef R2SOURCEDECOMPILER_H
|
||||
#define R2SOURCEDECOMPILER_H
|
||||
|
||||
#include "Decompiler.h"
|
||||
|
||||
class R2SourceDecompiler : public R2JsonDecompiler
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit R2SourceDecompiler(QObject *parent = nullptr);
|
||||
|
||||
static bool isAvailable();
|
||||
};
|
||||
|
||||
#endif // R2SOURCEDECOMPILER_H
|
||||
@@ -1,144 +1,13 @@
|
||||
|
||||
#include "R2pdcCmdDecompiler.h"
|
||||
#include "CodeMetaRange.h"
|
||||
#include "Iaito.h"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
|
||||
static RSyntaxHighlightType syntaxHighlightTypeFromString(const QString &str)
|
||||
{
|
||||
if (str == "keyword") {
|
||||
return R_SYNTAX_HIGHLIGHT_TYPE_KEYWORD;
|
||||
} else if (str == "comment") {
|
||||
return R_SYNTAX_HIGHLIGHT_TYPE_COMMENT;
|
||||
} else if (str == "datatype") {
|
||||
return R_SYNTAX_HIGHLIGHT_TYPE_DATATYPE;
|
||||
} else if (str == "function_name") {
|
||||
return R_SYNTAX_HIGHLIGHT_TYPE_FUNCTION_NAME;
|
||||
} else if (str == "function_parameter") {
|
||||
return R_SYNTAX_HIGHLIGHT_TYPE_FUNCTION_PARAMETER;
|
||||
} else if (str == "local_variable") {
|
||||
return R_SYNTAX_HIGHLIGHT_TYPE_LOCAL_VARIABLE;
|
||||
} else if (str == "constant_variable") {
|
||||
return R_SYNTAX_HIGHLIGHT_TYPE_CONSTANT_VARIABLE;
|
||||
} else if (str == "global_variable") {
|
||||
return R_SYNTAX_HIGHLIGHT_TYPE_GLOBAL_VARIABLE;
|
||||
}
|
||||
return R_SYNTAX_HIGHLIGHT_TYPE_KEYWORD;
|
||||
}
|
||||
|
||||
static void parseCodeMetaJson(RCodeMeta *code, const QJsonArray &annotations, int codeLength)
|
||||
{
|
||||
for (const auto &line : annotations) {
|
||||
QJsonObject obj = line.toObject();
|
||||
if (obj.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
QString type = obj["type"].toString();
|
||||
auto range = codeMetaRangeFromJson(obj, static_cast<size_t>(codeLength));
|
||||
if (!range) {
|
||||
continue;
|
||||
}
|
||||
RCodeMetaItem *mi = r_codemeta_item_new();
|
||||
mi->start = range->start;
|
||||
mi->end = range->end;
|
||||
|
||||
if (type == "offset") {
|
||||
mi->type = R_CODEMETA_TYPE_OFFSET;
|
||||
bool ok;
|
||||
mi->offset.offset = obj["offset"].toVariant().toULongLong(&ok);
|
||||
} else if (type == "syntax_highlight") {
|
||||
mi->type = R_CODEMETA_TYPE_SYNTAX_HIGHLIGHT;
|
||||
mi->syntax_highlight.type = syntaxHighlightTypeFromString(
|
||||
obj["syntax_highlight"].toString());
|
||||
} else if (type == "function_name") {
|
||||
mi->type = R_CODEMETA_TYPE_FUNCTION_NAME;
|
||||
mi->reference.name = strdup(obj["name"].toString().toUtf8().constData());
|
||||
bool ok;
|
||||
mi->reference.offset = obj["offset"].toVariant().toULongLong(&ok);
|
||||
} else if (type == "global_variable") {
|
||||
mi->type = R_CODEMETA_TYPE_GLOBAL_VARIABLE;
|
||||
bool ok;
|
||||
mi->reference.offset = obj["offset"].toVariant().toULongLong(&ok);
|
||||
} else if (type == "constant_variable") {
|
||||
mi->type = R_CODEMETA_TYPE_CONSTANT_VARIABLE;
|
||||
bool ok;
|
||||
mi->reference.offset = obj["offset"].toVariant().toULongLong(&ok);
|
||||
} else if (type == "local_variable") {
|
||||
mi->type = R_CODEMETA_TYPE_LOCAL_VARIABLE;
|
||||
mi->variable.name = strdup(obj["name"].toString().toUtf8().constData());
|
||||
} else if (type == "function_parameter") {
|
||||
mi->type = R_CODEMETA_TYPE_FUNCTION_PARAMETER;
|
||||
mi->variable.name = strdup(obj["name"].toString().toUtf8().constData());
|
||||
} else {
|
||||
r_codemeta_item_free(mi);
|
||||
continue;
|
||||
}
|
||||
r_codemeta_add_item(code, mi);
|
||||
}
|
||||
}
|
||||
|
||||
R2pdcCmdDecompiler::R2pdcCmdDecompiler(QObject *parent)
|
||||
: Decompiler("pdc", "pdc", parent)
|
||||
{
|
||||
task = nullptr;
|
||||
}
|
||||
: R2JsonDecompiler(
|
||||
"pdc", "pdc", QStringLiteral("pdcj"), QStringLiteral("pdc"), AnnotationMode::Full, parent)
|
||||
{}
|
||||
|
||||
bool R2pdcCmdDecompiler::isAvailable()
|
||||
{
|
||||
return Core()->cmdList("e cmd.pdc=?").contains(QStringLiteral("pdc"));
|
||||
}
|
||||
|
||||
RCodeMeta *R2pdcCmdDecompiler::decompileSync(RVA addr)
|
||||
{
|
||||
auto document = Core()->cmdj("pdcj @ " + QString::number(addr));
|
||||
QJsonObject json = document.object();
|
||||
if (json.isEmpty()) {
|
||||
return NULL;
|
||||
}
|
||||
QString codeString = json["code"].toString();
|
||||
RCodeMeta *code = r_codemeta_new("");
|
||||
parseCodeMetaJson(code, json["annotations"].toArray(), codeString.size());
|
||||
|
||||
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 R2pdcCmdDecompiler::decompileAt(RVA addr)
|
||||
{
|
||||
if (task) {
|
||||
return;
|
||||
}
|
||||
task = new R2Task("pdcj @ " + 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 pdc")));
|
||||
return;
|
||||
}
|
||||
QString codeString = json["code"].toString();
|
||||
RCodeMeta *code = r_codemeta_new("");
|
||||
parseCodeMetaJson(code, json["annotations"].toArray(), codeString.size());
|
||||
|
||||
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,13 @@
|
||||
#define R2PDC_DECOMPILER_H
|
||||
|
||||
#include "Decompiler.h"
|
||||
#include "IaitoCommon.h"
|
||||
#include "R2Task.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
class R2pdcCmdDecompiler : public Decompiler
|
||||
class R2pdcCmdDecompiler : public R2JsonDecompiler
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
R2Task *task;
|
||||
|
||||
public:
|
||||
explicit R2pdcCmdDecompiler(QObject *parent = nullptr);
|
||||
void decompileAt(RVA addr) override;
|
||||
RCodeMeta *decompileSync(RVA addr) override;
|
||||
|
||||
bool isRunning() override { return task != nullptr; }
|
||||
|
||||
static bool isAvailable();
|
||||
};
|
||||
|
||||
@@ -1,128 +1,18 @@
|
||||
|
||||
#include "R2retdecDecompiler.h"
|
||||
#include "CodeMetaRange.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());
|
||||
}
|
||||
*/
|
||||
|
||||
R2retdecDecompiler::R2retdecDecompiler(QObject *parent)
|
||||
: Decompiler("pdz", "pdz", parent)
|
||||
{
|
||||
task = nullptr;
|
||||
}
|
||||
: R2JsonDecompiler(
|
||||
"pdz",
|
||||
"pdz",
|
||||
QStringLiteral("pdzj"),
|
||||
QStringLiteral("retdec"),
|
||||
AnnotationMode::OffsetsOnly,
|
||||
parent)
|
||||
{}
|
||||
|
||||
bool R2retdecDecompiler::isAvailable()
|
||||
{
|
||||
return Core()->cmdList("e cmd.pdc=?").contains(QStringLiteral("pdz"));
|
||||
}
|
||||
|
||||
RCodeMeta *R2retdecDecompiler::decompileSync(RVA addr)
|
||||
{
|
||||
auto document = Core()->cmdj("pdzj @ " + 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;
|
||||
}
|
||||
auto range = codeMetaRangeFromJson(lineObject, static_cast<size_t>(codeString.size()));
|
||||
if (!range) {
|
||||
continue;
|
||||
}
|
||||
RCodeMetaItem *mi = r_codemeta_item_new();
|
||||
mi->start = range->start;
|
||||
mi->end = range->end;
|
||||
bool ok = false;
|
||||
mi->type = R_CODEMETA_TYPE_OFFSET;
|
||||
mi->offset.offset = lineObject["offset"].toVariant().toULongLong(&ok);
|
||||
if (!ok) {
|
||||
r_codemeta_item_free(mi);
|
||||
continue;
|
||||
}
|
||||
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 R2retdecDecompiler::decompileAt(RVA addr)
|
||||
{
|
||||
if (task) {
|
||||
return;
|
||||
}
|
||||
task = new R2Task("pdzj @ " + 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 retdec")));
|
||||
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;
|
||||
}
|
||||
auto range = codeMetaRangeFromJson(lineObject, static_cast<size_t>(codeString.size()));
|
||||
if (!range) {
|
||||
continue;
|
||||
}
|
||||
RCodeMetaItem *mi = r_codemeta_item_new();
|
||||
mi->start = range->start;
|
||||
mi->end = range->end;
|
||||
bool ok = false;
|
||||
mi->type = R_CODEMETA_TYPE_OFFSET;
|
||||
mi->offset.offset = lineObject["offset"].toVariant().toULongLong(&ok);
|
||||
if (!ok) {
|
||||
r_codemeta_item_free(mi);
|
||||
continue;
|
||||
}
|
||||
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,27 +2,14 @@
|
||||
#define R2RETDEC_DECOMPILER_H
|
||||
|
||||
#include "Decompiler.h"
|
||||
#include "IaitoCommon.h"
|
||||
#include "R2Task.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
class R2retdecDecompiler : public Decompiler
|
||||
class R2retdecDecompiler : public R2JsonDecompiler
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
R2Task *task;
|
||||
|
||||
public:
|
||||
explicit R2retdecDecompiler(QObject *parent = nullptr);
|
||||
|
||||
RCodeMeta *decompileSync(RVA addr) override;
|
||||
void decompileAt(RVA addr) override;
|
||||
|
||||
bool isRunning() override { return task != nullptr; }
|
||||
|
||||
static bool isAvailable();
|
||||
};
|
||||
|
||||
|
||||
@@ -21,6 +21,17 @@
|
||||
|
||||
#include <r_core.h>
|
||||
|
||||
#if R2_VERSION_NUMBER < 60000
|
||||
#error "iaito requires radare2 6.0.0 or newer"
|
||||
#endif
|
||||
|
||||
// The complete generic RBin resource API is available starting with ABI 117.
|
||||
#if defined(R2_ABIVERSION) && R2_ABIVERSION >= 117
|
||||
#define IAITO_HAS_R_BIN_RESOURCES 1
|
||||
#else
|
||||
#define IAITO_HAS_R_BIN_RESOURCES 0
|
||||
#endif
|
||||
|
||||
#ifdef IAITO_RESTORE_QT_EMIT
|
||||
#pragma pop_macro("emit")
|
||||
#undef IAITO_RESTORE_QT_EMIT
|
||||
|
||||
@@ -95,7 +95,11 @@ QStringList Iaito::getTranslationsDirectories()
|
||||
result << "/usr/share/iaito/translations";
|
||||
result << "/app/share/iaito/translations";
|
||||
// loading from qt
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
auto qtpath = QLibraryInfo::path(QLibraryInfo::TranslationsPath);
|
||||
#else
|
||||
auto qtpath = QLibraryInfo::location(QLibraryInfo::TranslationsPath);
|
||||
#endif
|
||||
result << qtpath;
|
||||
printf("Loading translations path %s\n", qtpath.toStdString().c_str());
|
||||
return result;
|
||||
|
||||
@@ -0,0 +1,324 @@
|
||||
#include "SamplesDB.h"
|
||||
|
||||
#include <r_muta.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdlib>
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QCryptographicHash>
|
||||
#include <QDir>
|
||||
#include <QDirIterator>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QMutex>
|
||||
#include <QRunnable>
|
||||
#include <QSaveFile>
|
||||
#include <QStandardPaths>
|
||||
#include <QThreadPool>
|
||||
|
||||
namespace {
|
||||
|
||||
// Heap-allocated and never freed: a process-lifetime mutex so a background
|
||||
// worker that is still finishing during shutdown can never lock a destroyed
|
||||
// object (function-local statics are destroyed at exit).
|
||||
QMutex &dbMutex()
|
||||
{
|
||||
static QMutex *m = new QMutex();
|
||||
return *m;
|
||||
}
|
||||
|
||||
QThreadPool &pool()
|
||||
{
|
||||
static QThreadPool *p = [] {
|
||||
auto *tp = new QThreadPool();
|
||||
tp->setMaxThreadCount(2);
|
||||
return tp;
|
||||
}();
|
||||
return *p;
|
||||
}
|
||||
|
||||
std::atomic<bool> g_shutdown{false};
|
||||
|
||||
class FnRunnable : public QRunnable
|
||||
{
|
||||
public:
|
||||
explicit FnRunnable(std::function<void()> fn)
|
||||
: m_fn(std::move(fn))
|
||||
{
|
||||
setAutoDelete(true);
|
||||
}
|
||||
void run() override { m_fn(); }
|
||||
|
||||
private:
|
||||
std::function<void()> m_fn;
|
||||
};
|
||||
|
||||
bool isValidSha256(const QString &h)
|
||||
{
|
||||
if (h.size() != 64) {
|
||||
return false;
|
||||
}
|
||||
for (const QChar &c : h) {
|
||||
if (!((c >= QLatin1Char('0') && c <= QLatin1Char('9'))
|
||||
|| (c >= QLatin1Char('a') && c <= QLatin1Char('f')))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// samples/<h0:2>/<h2:4>/<h0:4> bucket file holding "<sha256> <path>" lines.
|
||||
QString bucketFile(const QString &h)
|
||||
{
|
||||
return QStringLiteral("%1/%2/%3/%4")
|
||||
.arg(SamplesDB::dbRoot(), h.mid(0, 2), h.mid(2, 2), h.mid(0, 4));
|
||||
}
|
||||
|
||||
QList<QPair<QString, QString>> readBucketFile(const QString &file)
|
||||
{
|
||||
QList<QPair<QString, QString>> out;
|
||||
QFile f(file);
|
||||
if (!f.open(QIODevice::ReadOnly)) {
|
||||
return out;
|
||||
}
|
||||
const QList<QByteArray> lines = f.readAll().split('\n');
|
||||
for (const QByteArray &raw : lines) {
|
||||
QString line = QString::fromUtf8(raw);
|
||||
// Strip only the line terminator; paths may legitimately end in spaces.
|
||||
while (line.endsWith(QLatin1Char('\r'))) {
|
||||
line.chop(1);
|
||||
}
|
||||
const int sp = line.indexOf(QLatin1Char(' '));
|
||||
if (sp <= 0) {
|
||||
continue;
|
||||
}
|
||||
const QString path = line.mid(sp + 1);
|
||||
if (!path.isEmpty()) {
|
||||
out.append({line.left(sp), path});
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
bool writeBucketFile(const QString &file, const QList<QPair<QString, QString>> &entries)
|
||||
{
|
||||
if (entries.isEmpty()) {
|
||||
return QFile::remove(file) || !QFile::exists(file);
|
||||
}
|
||||
QDir().mkpath(QFileInfo(file).absolutePath());
|
||||
// QSaveFile writes to a temp file and atomically renames over the target on
|
||||
// commit, so the live bucket is never truncated mid-write.
|
||||
QSaveFile f(file);
|
||||
if (!f.open(QIODevice::WriteOnly)) {
|
||||
return false;
|
||||
}
|
||||
QByteArray data;
|
||||
for (const auto &e : entries) {
|
||||
data += e.first.toUtf8();
|
||||
data += ' ';
|
||||
data += e.second.toUtf8();
|
||||
data += '\n';
|
||||
}
|
||||
if (f.write(data) != data.size()) {
|
||||
f.cancelWriting();
|
||||
return false;
|
||||
}
|
||||
return f.commit();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace SamplesDB {
|
||||
|
||||
QString dbRoot()
|
||||
{
|
||||
return QDir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation))
|
||||
.filePath(QStringLiteral("samples"));
|
||||
}
|
||||
|
||||
QString sha256File(const QString &path)
|
||||
{
|
||||
QFile f(path);
|
||||
if (!f.open(QIODevice::ReadOnly)) {
|
||||
return QString();
|
||||
}
|
||||
RMuta *muta = r_muta_new();
|
||||
if (!muta) {
|
||||
return QString();
|
||||
}
|
||||
RMutaSession *session = r_muta_use(muta, "sha256");
|
||||
if (!session) {
|
||||
r_muta_free(muta);
|
||||
return QString();
|
||||
}
|
||||
|
||||
const qint64 blockSize = 1024 * 1024;
|
||||
QByteArray buf(blockSize, Qt::Uninitialized);
|
||||
bool ok = true;
|
||||
bool readAny = false;
|
||||
qint64 n;
|
||||
while ((n = f.read(buf.data(), blockSize)) > 0) {
|
||||
readAny = true;
|
||||
// Abort promptly on shutdown so a huge in-flight hash can't stall exit.
|
||||
if (g_shutdown.load()) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
if (!r_muta_session_update(
|
||||
session, reinterpret_cast<const ut8 *>(buf.constData()), static_cast<int>(n))) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (n < 0) {
|
||||
ok = false;
|
||||
}
|
||||
QString hex;
|
||||
if (ok) {
|
||||
if (readAny) {
|
||||
ok = r_muta_session_end(session, nullptr, 0);
|
||||
if (ok) {
|
||||
int digestSize = 0;
|
||||
ut8 *digest = r_muta_session_get_output(session, &digestSize);
|
||||
if (digest && digestSize > 0) {
|
||||
hex = QString::fromLatin1(
|
||||
QByteArray(reinterpret_cast<const char *>(digest), digestSize).toHex());
|
||||
} else {
|
||||
ok = false;
|
||||
}
|
||||
std::free(digest);
|
||||
}
|
||||
} else {
|
||||
hex = QString::fromLatin1(
|
||||
QCryptographicHash::hash(QByteArray(), QCryptographicHash::Sha256).toHex());
|
||||
}
|
||||
}
|
||||
r_muta_session_free(session);
|
||||
r_muta_free(muta);
|
||||
return ok ? hex : QString();
|
||||
}
|
||||
|
||||
QString registerFile(const QString &path)
|
||||
{
|
||||
const QFileInfo fi(path);
|
||||
if (!fi.exists() || !fi.isFile()) {
|
||||
return QString();
|
||||
}
|
||||
const QString abs = fi.absoluteFilePath();
|
||||
if (abs.contains(QLatin1Char('\n')) || abs.contains(QLatin1Char('\r'))) {
|
||||
return QString();
|
||||
}
|
||||
const QString h = sha256File(abs);
|
||||
if (!isValidSha256(h)) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
QMutexLocker lock(&dbMutex());
|
||||
const QString file = bucketFile(h);
|
||||
auto entries = readBucketFile(file);
|
||||
bool found = false;
|
||||
for (auto &e : entries) {
|
||||
if (e.first == h) {
|
||||
if (e.second == abs) {
|
||||
return h;
|
||||
}
|
||||
e.second = abs;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
entries.append({h, abs});
|
||||
}
|
||||
return writeBucketFile(file, entries) ? h : QString();
|
||||
}
|
||||
|
||||
void registerFileAsync(const QString &path)
|
||||
{
|
||||
if (g_shutdown.load()) {
|
||||
return;
|
||||
}
|
||||
const QString p = path;
|
||||
pool().start(new FnRunnable([p]() { registerFile(p); }));
|
||||
}
|
||||
|
||||
void scanFolderAsync(const QString &dir, std::function<void()> onComplete)
|
||||
{
|
||||
if (g_shutdown.load()) {
|
||||
return;
|
||||
}
|
||||
const QString d = dir;
|
||||
pool().start(new FnRunnable([d, onComplete]() {
|
||||
const QString rootPrefix = dbRoot() + QLatin1Char('/');
|
||||
QDirIterator it(d, QDir::Files, QDirIterator::Subdirectories);
|
||||
while (it.hasNext() && !g_shutdown.load()) {
|
||||
const QString f = it.next();
|
||||
// Never register the database's own bucket files.
|
||||
if (QFileInfo(f).absoluteFilePath().startsWith(rootPrefix)) {
|
||||
continue;
|
||||
}
|
||||
registerFile(f);
|
||||
}
|
||||
if (onComplete) {
|
||||
onComplete();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
void waitForPending()
|
||||
{
|
||||
g_shutdown.store(true);
|
||||
pool().clear();
|
||||
pool().waitForDone();
|
||||
}
|
||||
|
||||
QString pathForHash(const QString &sha256)
|
||||
{
|
||||
const QString h = sha256.toLower();
|
||||
if (!isValidSha256(h)) {
|
||||
return QString();
|
||||
}
|
||||
QMutexLocker lock(&dbMutex());
|
||||
const auto entries = readBucketFile(bucketFile(h));
|
||||
for (const auto &e : entries) {
|
||||
if (e.first == h) {
|
||||
return e.second;
|
||||
}
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
bool remove(const QString &sha256)
|
||||
{
|
||||
const QString h = sha256.toLower();
|
||||
if (!isValidSha256(h)) {
|
||||
return false;
|
||||
}
|
||||
QMutexLocker lock(&dbMutex());
|
||||
const QString file = bucketFile(h);
|
||||
const auto entries = readBucketFile(file);
|
||||
QList<QPair<QString, QString>> kept;
|
||||
for (const auto &e : entries) {
|
||||
if (e.first != h) {
|
||||
kept.append(e);
|
||||
}
|
||||
}
|
||||
if (kept.size() == entries.size()) {
|
||||
return false;
|
||||
}
|
||||
return writeBucketFile(file, kept);
|
||||
}
|
||||
|
||||
QList<QPair<QString, QString>> all()
|
||||
{
|
||||
QList<QPair<QString, QString>> out;
|
||||
QMutexLocker lock(&dbMutex());
|
||||
QDirIterator it(dbRoot(), QDir::Files, QDirIterator::Subdirectories);
|
||||
while (it.hasNext()) {
|
||||
out += readBucketFile(it.next());
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace SamplesDB
|
||||
@@ -0,0 +1,50 @@
|
||||
#ifndef IAITO_SAMPLESDB_H
|
||||
#define IAITO_SAMPLESDB_H
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <QList>
|
||||
#include <QPair>
|
||||
#include <QString>
|
||||
|
||||
// On-disk database mapping sha256 <-> absolute file path. Entries are sharded
|
||||
// git/rainbow-table style across two hash-prefix directory levels, each leaf
|
||||
// being a plain text file with "<sha256> <path>" lines. See doc/deeplinks.md.
|
||||
namespace SamplesDB {
|
||||
|
||||
// Absolute path of the database root (lazily created).
|
||||
QString dbRoot();
|
||||
|
||||
// Computes the lowercase hex sha256 of a file using radare2's r_muta API in
|
||||
// fixed-size blocks (bounded memory for huge files). Returns an
|
||||
// empty string on failure.
|
||||
QString sha256File(const QString &path);
|
||||
|
||||
// Hashes path and stores the sha256 -> absolute path mapping (latest path wins).
|
||||
// Idempotent. Returns the computed sha256, or an empty string on failure.
|
||||
QString registerFile(const QString &path);
|
||||
|
||||
// registerFile on a background worker (hashing must not block the UI thread).
|
||||
void registerFileAsync(const QString &path);
|
||||
|
||||
// Recursively registers every file under dir on a background worker, skipping
|
||||
// the database's own storage. onComplete (if set) runs on the worker thread
|
||||
// once the scan finishes; marshal back to the UI thread yourself.
|
||||
void scanFolderAsync(const QString &dir, std::function<void()> onComplete);
|
||||
|
||||
// Stops accepting new work and blocks until pending workers finish. Call once
|
||||
// on application shutdown so no worker outlives the process.
|
||||
void waitForPending();
|
||||
|
||||
// Returns the path registered for sha256, or an empty string if unknown.
|
||||
QString pathForHash(const QString &sha256);
|
||||
|
||||
// Removes the entry for sha256. Returns true if something was removed.
|
||||
bool remove(const QString &sha256);
|
||||
|
||||
// Returns every {sha256, path} pair stored in the database.
|
||||
QList<QPair<QString, QString>> all();
|
||||
|
||||
} // namespace SamplesDB
|
||||
|
||||
#endif // IAITO_SAMPLESDB_H
|
||||
@@ -53,8 +53,7 @@ QList<QTextEdit::ExtraSelection> createHighlightSelections(
|
||||
highlightSelection.cursor = document->find(text, highlightSelection.cursor);
|
||||
|
||||
if (!highlightSelection.cursor.isNull()) {
|
||||
highlightSelection.format.setBackground(QColor(0xe8, 0xbb, 0x32, 0xc0));
|
||||
highlightSelection.format.setForeground(QColor(Qt::black));
|
||||
highlightSelection.format.setBackground(ConfigColor("wordHighlight"));
|
||||
selections.append(highlightSelection);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
#ifndef SHORTCUTMANAGER_H
|
||||
#define SHORTCUTMANAGER_H
|
||||
|
||||
#include "core/IaitoCommon.h"
|
||||
#include <QHash>
|
||||
#include <QKeySequence>
|
||||
#include <QList>
|
||||
#include <QObject>
|
||||
#include <QPointer>
|
||||
#include <QSettings>
|
||||
#include <QVector>
|
||||
|
||||
#define ShortcutMgr() (ShortcutManager::instance())
|
||||
|
||||
class QShortcut;
|
||||
class QAction;
|
||||
class QWidget;
|
||||
class QKeyEvent;
|
||||
|
||||
enum class ShortcutScope {
|
||||
Global,
|
||||
Disassembly,
|
||||
Decompiler,
|
||||
Hexdump,
|
||||
Graph,
|
||||
GraphOverview,
|
||||
Console,
|
||||
AddressableList,
|
||||
Debug,
|
||||
Overview,
|
||||
};
|
||||
|
||||
struct ShortcutConflict
|
||||
{
|
||||
QString idA;
|
||||
QString idB;
|
||||
QKeySequence sequence;
|
||||
};
|
||||
|
||||
struct CommandShortcut
|
||||
{
|
||||
QKeySequence key;
|
||||
QString command;
|
||||
bool needsInput = false;
|
||||
};
|
||||
|
||||
class IAITO_EXPORT ShortcutManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
static ShortcutManager *instance();
|
||||
|
||||
void load();
|
||||
|
||||
QStringList ids() const;
|
||||
static QList<ShortcutScope> scopes();
|
||||
QStringList idsForScope(ShortcutScope scope) const;
|
||||
static QString scopeName(ShortcutScope scope);
|
||||
QString displayName(const QString &id) const;
|
||||
ShortcutScope scope(const QString &id) const;
|
||||
bool contains(const QString &id) const;
|
||||
|
||||
QList<QKeySequence> sequences(const QString &id) const;
|
||||
QKeySequence sequence(const QString &id) const;
|
||||
QList<QKeySequence> defaults(const QString &id) const;
|
||||
bool isOverridden(const QString &id) const;
|
||||
|
||||
void setSequences(const QString &id, const QList<QKeySequence> &seqs);
|
||||
void setSequence(const QString &id, const QKeySequence &seq);
|
||||
void resetToDefault(const QString &id);
|
||||
void resetAll();
|
||||
|
||||
QShortcut *registerShortcut(
|
||||
const QString &id, QWidget *parent, Qt::ShortcutContext context = Qt::WindowShortcut);
|
||||
void bindAction(const QString &id, QAction *action);
|
||||
|
||||
bool matches(const QString &id, const QKeyEvent *event) const;
|
||||
|
||||
QList<ShortcutConflict> conflicts() const;
|
||||
|
||||
QVector<CommandShortcut> commandShortcuts() const;
|
||||
void setCommandShortcuts(const QVector<CommandShortcut> &list);
|
||||
int matchCommandShortcut(const QKeyEvent *event) const;
|
||||
void migrateLegacyCommandKeys();
|
||||
|
||||
signals:
|
||||
void shortcutChanged(const QString &id);
|
||||
void shortcutsReset();
|
||||
void commandShortcutsChanged();
|
||||
|
||||
private:
|
||||
explicit ShortcutManager(QObject *parent = nullptr);
|
||||
void applyTo(const QString &id);
|
||||
void persist(const QString &id);
|
||||
|
||||
struct Binding
|
||||
{
|
||||
QPointer<QShortcut> shortcut;
|
||||
QPointer<QAction> action;
|
||||
int seqIndex = 0;
|
||||
};
|
||||
|
||||
QSettings s;
|
||||
QHash<QString, QList<QKeySequence>> m_overrides;
|
||||
QHash<QString, QVector<Binding>> m_bindings;
|
||||
QVector<CommandShortcut> m_commandShortcuts;
|
||||
};
|
||||
|
||||
#endif // SHORTCUTMANAGER_H
|
||||
@@ -0,0 +1,30 @@
|
||||
#include "SourceLineReference.h"
|
||||
|
||||
QList<SourceLineReference> SourceLineReference::parse(const QString &contents)
|
||||
{
|
||||
QList<SourceLineReference> result;
|
||||
for (const QString &row : contents.split(QLatin1Char('\n'))) {
|
||||
const int firstTab = row.indexOf(QLatin1Char('\t'));
|
||||
const int secondTab = row.indexOf(QLatin1Char('\t'), firstTab + 1);
|
||||
if (firstTab <= 0 || secondTab < 0) {
|
||||
continue;
|
||||
}
|
||||
const QString addressText = row.left(firstTab);
|
||||
if (!addressText.startsWith(QStringLiteral("0x"))) {
|
||||
continue;
|
||||
}
|
||||
bool addressOk = false;
|
||||
bool lineOk = false;
|
||||
const quint64 address = addressText.mid(2).toULongLong(&addressOk, 16);
|
||||
const int line = row.mid(firstTab + 1, secondTab - firstTab - 1).toInt(&lineOk);
|
||||
if (!addressOk || !lineOk || line < 0) {
|
||||
continue;
|
||||
}
|
||||
QString code = row.mid(secondTab + 1);
|
||||
if (code.endsWith(QLatin1Char('\r'))) {
|
||||
code.chop(1);
|
||||
}
|
||||
result.append({address, line, code});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
|
||||
struct SourceLineReference
|
||||
{
|
||||
quint64 address;
|
||||
int line;
|
||||
QString code;
|
||||
|
||||
// fs.r2's /cl files contain address, line number and source text, separated by tabs.
|
||||
static QList<SourceLineReference> parse(const QString &contents);
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
#ifndef SPARSEHEXLAYOUT_H
|
||||
#define SPARSEHEXLAYOUT_H
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
namespace SparseHexLayout {
|
||||
|
||||
// Offsets are relative to a bounded read buffer, never absolute addresses.
|
||||
struct Row
|
||||
{
|
||||
int offset;
|
||||
int size;
|
||||
int fill = -1;
|
||||
bool continues = false;
|
||||
};
|
||||
|
||||
inline int uniformPrefix(const unsigned char *bytes, int size)
|
||||
{
|
||||
const uint64_t pattern = bytes[0] ? UINT64_MAX : 0;
|
||||
int i = 0;
|
||||
while (size - i >= int(sizeof(pattern))) {
|
||||
uint64_t word;
|
||||
std::memcpy(&word, bytes + i, sizeof(word));
|
||||
if (word != pattern) {
|
||||
break;
|
||||
}
|
||||
i += sizeof(word);
|
||||
}
|
||||
while (i < size && bytes[i] == bytes[0]) {
|
||||
++i;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
inline std::vector<Row> build(
|
||||
const unsigned char *bytes, int size, int rowSize, int itemSize, int maxRows)
|
||||
{
|
||||
std::vector<Row> rows;
|
||||
if (size <= 0 || rowSize <= 0 || itemSize <= 0 || maxRows <= 0) {
|
||||
return rows;
|
||||
}
|
||||
rows.reserve(std::min(maxRows, size / rowSize + 2));
|
||||
int begin = 0;
|
||||
int pos = 0;
|
||||
while (pos < size && int(rows.size()) < maxRows) {
|
||||
int step = itemSize;
|
||||
if (bytes[pos] == 0 || bytes[pos] == 0xff) {
|
||||
const int run = uniformPrefix(bytes + pos, size - pos);
|
||||
if (run > rowSize * 2) {
|
||||
if (pos > begin) {
|
||||
rows.push_back({begin, pos - begin});
|
||||
if (int(rows.size()) == maxRows) {
|
||||
return rows;
|
||||
}
|
||||
}
|
||||
// Keep multi-byte items intact, including at the end of a read.
|
||||
const int folded = run - run % itemSize;
|
||||
rows.push_back({pos, folded, bytes[pos], pos + run == size});
|
||||
begin = pos = pos + folded;
|
||||
continue;
|
||||
}
|
||||
// Consume short runs once too; rescanning every suffix is quadratic
|
||||
// when the user selects a large number of columns.
|
||||
step = std::max(itemSize, run - run % itemSize);
|
||||
}
|
||||
step = std::min(step, size - pos);
|
||||
while (step > 0 && int(rows.size()) < maxRows) {
|
||||
const int count = std::min(step, rowSize - (pos - begin));
|
||||
pos += count;
|
||||
step -= count;
|
||||
if (pos - begin == rowSize) {
|
||||
rows.push_back({begin, rowSize});
|
||||
begin = pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (pos > begin && int(rows.size()) < maxRows) {
|
||||
rows.push_back({begin, pos - begin});
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
} // namespace SparseHexLayout
|
||||
|
||||
#endif // SPARSEHEXLAYOUT_H
|
||||
@@ -0,0 +1,253 @@
|
||||
#include "TypeLayout.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <QRegularExpression>
|
||||
|
||||
void TypeLayout::load(const QString &text, int pointerByteSize)
|
||||
{
|
||||
database.clear();
|
||||
sizes.clear();
|
||||
pointerSize = qBound(1, pointerByteSize, 8);
|
||||
for (const QString &line : text.split(QLatin1Char('\n'))) {
|
||||
const int separator = line.indexOf(QLatin1Char('='));
|
||||
if (separator > 0) {
|
||||
database.insert(line.left(separator), line.mid(separator + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QStringList TypeLayout::names() const
|
||||
{
|
||||
QStringList result;
|
||||
for (auto it = database.cbegin(); it != database.cend(); ++it) {
|
||||
if (it.value() == QLatin1String("struct") || it.value() == QLatin1String("union")
|
||||
|| it.value() == QLatin1String("typedef") || it.value() == QLatin1String("type")
|
||||
|| it.value() == QLatin1String("enum")) {
|
||||
result.append(it.key());
|
||||
}
|
||||
}
|
||||
result.sort(Qt::CaseInsensitive);
|
||||
return result;
|
||||
}
|
||||
|
||||
QMap<quint64, QString> TypeLayout::links() const
|
||||
{
|
||||
QMap<quint64, QString> result;
|
||||
for (auto it = database.cbegin(); it != database.cend(); ++it) {
|
||||
if (it.key().startsWith(QLatin1String("link."))) {
|
||||
bool ok = false;
|
||||
const quint64 address = it.key().mid(5).toULongLong(&ok, 16);
|
||||
if (ok) {
|
||||
result.insert(address, it.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QString TypeLayout::resolve(QString type) const
|
||||
{
|
||||
static const QRegularExpression qualifiers(QStringLiteral("\\b(const|volatile|restrict)\\b"));
|
||||
static const QRegularExpression tag(QStringLiteral("^(struct|union|enum)\\s+"));
|
||||
QStringList visited;
|
||||
for (int i = 0; i < 32; ++i) {
|
||||
type.remove(qualifiers);
|
||||
type = type.simplified();
|
||||
type.remove(tag);
|
||||
if (visited.contains(type)) {
|
||||
return QString();
|
||||
}
|
||||
visited.append(type);
|
||||
if (database.value(type) != QLatin1String("typedef")) {
|
||||
return type;
|
||||
}
|
||||
type = database.value(QStringLiteral("typedef.") + type);
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
bool TypeLayout::array(const QString &type, QString *element, quint64 *count)
|
||||
{
|
||||
static const QRegularExpression expression(QStringLiteral("^(.*?)\\[(\\d+)\\](.*)$"));
|
||||
const auto match = expression.match(type);
|
||||
if (!match.hasMatch()) {
|
||||
return false;
|
||||
}
|
||||
bool ok = false;
|
||||
*count = match.captured(2).toULongLong(&ok);
|
||||
*element = (match.captured(1) + match.captured(3)).trimmed();
|
||||
return ok;
|
||||
}
|
||||
|
||||
QString TypeLayout::kind(const QString &type) const
|
||||
{
|
||||
const QString resolved = resolve(type);
|
||||
if (resolved.endsWith(QLatin1Char('*'))) {
|
||||
return QStringLiteral("pointer");
|
||||
}
|
||||
QString element;
|
||||
quint64 count;
|
||||
if (array(resolved, &element, &count)) {
|
||||
return QStringLiteral("array");
|
||||
}
|
||||
return database.value(resolved);
|
||||
}
|
||||
|
||||
QVector<TypeLayout::Field> TypeLayout::fields(const QString &type) const
|
||||
{
|
||||
return fields(type, {resolve(type)});
|
||||
}
|
||||
|
||||
QVector<TypeLayout::Field> TypeLayout::fields(const QString &type, const QStringList &ancestors) const
|
||||
{
|
||||
const QString resolved = resolve(type);
|
||||
const QString category = kind(resolved);
|
||||
QVector<Field> result;
|
||||
if (category != QLatin1String("struct") && category != QLatin1String("union")) {
|
||||
return result;
|
||||
}
|
||||
const QString prefix = category + QLatin1Char('.') + resolved;
|
||||
const QStringList members = database.value(prefix).split(QLatin1Char(','), Qt::SkipEmptyParts);
|
||||
bool allZero = true;
|
||||
for (const QString &name : members) {
|
||||
const QStringList details
|
||||
= database.value(prefix + QLatin1Char('.') + name).split(QLatin1Char(','));
|
||||
if (details.size() < 3) {
|
||||
continue;
|
||||
}
|
||||
bool ok = false;
|
||||
const quint64 offset = details.at(1).toULongLong(&ok, 0);
|
||||
if (!ok) {
|
||||
continue;
|
||||
}
|
||||
const quint64 count = details.at(2).toULongLong(&ok, 0);
|
||||
if (!ok) {
|
||||
continue;
|
||||
}
|
||||
result.append({name, details.first(), offset, qMax<quint64>(1, count)});
|
||||
allZero &= offset == 0;
|
||||
}
|
||||
// Some bundled r2 definitions omit offsets entirely. Their members are packed.
|
||||
if (allZero && category == QLatin1String("struct") && result.size() > 1) {
|
||||
quint64 offset = 0;
|
||||
for (Field &field : result) {
|
||||
field.offset = offset;
|
||||
const quint64 bytes = size(field.type, ancestors);
|
||||
if (bytes && field.count <= (std::numeric_limits<quint64>::max() - offset) / bytes) {
|
||||
offset += bytes * field.count;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
quint64 TypeLayout::size(const QString &type) const
|
||||
{
|
||||
const auto cached = sizes.constFind(type);
|
||||
if (cached != sizes.cend()) {
|
||||
return cached.value();
|
||||
}
|
||||
const quint64 result = size(type, {});
|
||||
sizes.insert(type, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
quint64 TypeLayout::size(const QString &type, QStringList ancestors) const
|
||||
{
|
||||
const QString resolved = resolve(type);
|
||||
if (resolved.isEmpty() || ancestors.size() >= 32 || ancestors.contains(resolved)) {
|
||||
return 0;
|
||||
}
|
||||
ancestors.append(resolved);
|
||||
const QString category = kind(resolved);
|
||||
if (category == QLatin1String("pointer")) {
|
||||
return pointerSize;
|
||||
}
|
||||
QString element;
|
||||
quint64 count;
|
||||
if (array(resolved, &element, &count)) {
|
||||
const quint64 stride = size(element, ancestors);
|
||||
return stride && count <= std::numeric_limits<quint64>::max() / stride ? stride * count : 0;
|
||||
}
|
||||
if (category == QLatin1String("enum")) {
|
||||
return 4;
|
||||
}
|
||||
if (category == QLatin1String("struct") || category == QLatin1String("union")) {
|
||||
quint64 end = 0;
|
||||
for (const Field &field : fields(resolved, ancestors)) {
|
||||
const quint64 stride = size(field.type, ancestors);
|
||||
if (!stride
|
||||
|| field.count > (std::numeric_limits<quint64>::max() - field.offset) / stride) {
|
||||
return 0;
|
||||
}
|
||||
end = qMax(end, field.offset + stride * field.count);
|
||||
}
|
||||
return end;
|
||||
}
|
||||
bool ok = false;
|
||||
const qint64 bits = database.value(QStringLiteral("type.") + resolved + QStringLiteral(".size"))
|
||||
.toLongLong(&ok);
|
||||
return ok && bits > 0 ? quint64(bits) / 8 : 0;
|
||||
}
|
||||
|
||||
quint64 TypeLayout::unsignedValue(const QByteArray &bytes, bool bigEndian)
|
||||
{
|
||||
quint64 result = 0;
|
||||
for (int i = 0; i < qMin(8, int(bytes.size())); ++i) {
|
||||
const int shift = (bigEndian ? bytes.size() - i - 1 : i) * 8;
|
||||
if (shift < 64) {
|
||||
result |= quint64(static_cast<unsigned char>(bytes.at(i))) << shift;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QString TypeLayout::value(const QString &type, const QByteArray &bytes, bool bigEndian) const
|
||||
{
|
||||
const QString resolved = resolve(type);
|
||||
const quint64 number = unsignedValue(bytes, bigEndian);
|
||||
if (kind(resolved) == QLatin1String("pointer")) {
|
||||
return QStringLiteral("0x%1").arg(number, 0, 16);
|
||||
}
|
||||
if (resolved == QLatin1String("float") && bytes.size() == 4) {
|
||||
const quint32 raw = quint32(number);
|
||||
float value;
|
||||
memcpy(&value, &raw, sizeof(value));
|
||||
return QString::number(value, 'g', 8);
|
||||
}
|
||||
if (resolved == QLatin1String("double") && bytes.size() == 8) {
|
||||
double value;
|
||||
memcpy(&value, &number, sizeof(value));
|
||||
return QString::number(value, 'g', 16);
|
||||
}
|
||||
if (bytes.size() > 8) {
|
||||
return QString::fromLatin1(bytes.toHex(' '));
|
||||
}
|
||||
const bool isUnsigned = resolved.startsWith(QLatin1String("uint"))
|
||||
|| resolved.startsWith(QLatin1String("unsigned"));
|
||||
quint64 extended = number;
|
||||
if (!isUnsigned && !bytes.isEmpty() && bytes.size() < 8
|
||||
&& (number & (quint64(1) << (bytes.size() * 8 - 1)))) {
|
||||
extended |= std::numeric_limits<quint64>::max() << (bytes.size() * 8);
|
||||
}
|
||||
qint64 signedNumber;
|
||||
memcpy(&signedNumber, &extended, sizeof(signedNumber));
|
||||
QString result = isUnsigned ? QString::number(number) : QString::number(signedNumber);
|
||||
if (kind(resolved) == QLatin1String("enum")) {
|
||||
const QString prefix = QStringLiteral("enum.") + resolved + QLatin1Char('.');
|
||||
for (auto it = database.cbegin(); it != database.cend(); ++it) {
|
||||
bool ok = false;
|
||||
const qint64 candidate = it.value().toLongLong(&ok, 0);
|
||||
if (it.key().startsWith(prefix) && ok && candidate == signedNumber) {
|
||||
result += QStringLiteral(" (%1)").arg(it.key().mid(prefix.size()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (resolved == QLatin1String("char") && number >= 32 && number < 127) {
|
||||
result += QStringLiteral(" '%1'").arg(QChar(ushort(number)));
|
||||
}
|
||||
return result + QStringLiteral(" (0x%1)").arg(number, 0, 16);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
#ifndef TYPELAYOUT_H
|
||||
#define TYPELAYOUT_H
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QHash>
|
||||
#include <QMap>
|
||||
#include <QStringList>
|
||||
#include <QVector>
|
||||
|
||||
// A snapshot of r2's type database. Keep stored offsets: pf formats and tsj can
|
||||
// lose padding, and JSON numbers cannot represent every 64-bit type-link address.
|
||||
class TypeLayout
|
||||
{
|
||||
public:
|
||||
struct Field
|
||||
{
|
||||
QString name;
|
||||
QString type;
|
||||
quint64 offset = 0;
|
||||
quint64 count = 1;
|
||||
};
|
||||
|
||||
void load(const QString &database, int pointerSize);
|
||||
QStringList names() const;
|
||||
QMap<quint64, QString> links() const;
|
||||
QString resolve(QString type) const;
|
||||
QString kind(const QString &type) const;
|
||||
QVector<Field> fields(const QString &type) const;
|
||||
quint64 size(const QString &type) const;
|
||||
QString value(const QString &type, const QByteArray &bytes, bool bigEndian) const;
|
||||
static quint64 unsignedValue(const QByteArray &bytes, bool bigEndian);
|
||||
static bool array(const QString &type, QString *element, quint64 *count);
|
||||
|
||||
private:
|
||||
QHash<QString, QString> database;
|
||||
int pointerSize = 8;
|
||||
mutable QHash<QString, quint64> sizes;
|
||||
QVector<Field> fields(const QString &type, const QStringList &ancestors) const;
|
||||
quint64 size(const QString &type, QStringList ancestors) const;
|
||||
};
|
||||
|
||||
#endif // TYPELAYOUT_H
|
||||
@@ -0,0 +1,70 @@
|
||||
#ifndef IAITO_STYLE_H
|
||||
#define IAITO_STYLE_H
|
||||
|
||||
#include "Theme.h"
|
||||
|
||||
#include <QCommonStyle>
|
||||
#include <QList>
|
||||
#include <QPointer>
|
||||
|
||||
class QTimer;
|
||||
|
||||
class IaitoStyle : public QCommonStyle
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit IaitoStyle();
|
||||
|
||||
static IaitoStyle *instance();
|
||||
|
||||
void setTheme(const Theme &theme);
|
||||
const Theme &theme() const { return m_theme; }
|
||||
|
||||
int styleHint(
|
||||
StyleHint hint,
|
||||
const QStyleOption *option = nullptr,
|
||||
const QWidget *widget = nullptr,
|
||||
QStyleHintReturn *returnData = nullptr) const override;
|
||||
|
||||
void drawPrimitive(
|
||||
PrimitiveElement element,
|
||||
const QStyleOption *option,
|
||||
QPainter *painter,
|
||||
const QWidget *widget = nullptr) const override;
|
||||
|
||||
void drawControl(
|
||||
ControlElement element,
|
||||
const QStyleOption *option,
|
||||
QPainter *painter,
|
||||
const QWidget *widget = nullptr) const override;
|
||||
|
||||
void drawComplexControl(
|
||||
ComplexControl control,
|
||||
const QStyleOptionComplex *option,
|
||||
QPainter *painter,
|
||||
const QWidget *widget = nullptr) const override;
|
||||
|
||||
int pixelMetric(
|
||||
PixelMetric metric,
|
||||
const QStyleOption *option = nullptr,
|
||||
const QWidget *widget = nullptr) const override;
|
||||
|
||||
QSize sizeFromContents(
|
||||
ContentsType type,
|
||||
const QStyleOption *option,
|
||||
const QSize &contentsSize,
|
||||
const QWidget *widget = nullptr) const override;
|
||||
|
||||
void polish(QWidget *widget) override;
|
||||
void unpolish(QWidget *widget) override;
|
||||
|
||||
private:
|
||||
static IaitoStyle *s_instance;
|
||||
Theme m_theme;
|
||||
QTimer *m_busyTimer;
|
||||
mutable QList<QPointer<QWidget>> m_busyBars;
|
||||
int m_busyPhase = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,353 @@
|
||||
#include "Theme.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QFile>
|
||||
#include <QSettings>
|
||||
|
||||
QColor ColorGroup::forState(QStyle::State state) const
|
||||
{
|
||||
if (!(state & QStyle::State_Enabled)) {
|
||||
return disabled;
|
||||
}
|
||||
if (state & QStyle::State_Sunken) {
|
||||
return pressed;
|
||||
}
|
||||
if (state & QStyle::State_On) {
|
||||
return checked;
|
||||
}
|
||||
if (state & QStyle::State_MouseOver) {
|
||||
return hovered;
|
||||
}
|
||||
if (state & QStyle::State_HasFocus) {
|
||||
return focused;
|
||||
}
|
||||
return normal;
|
||||
}
|
||||
|
||||
static ColorGroup deriveGroup(const QColor &base, bool dark)
|
||||
{
|
||||
ColorGroup g;
|
||||
g.normal = base;
|
||||
g.disabled = base;
|
||||
if (dark) {
|
||||
g.hovered = base.lighter(120);
|
||||
g.pressed = base.darker(115);
|
||||
g.checked = base.lighter(135);
|
||||
g.focused = base.lighter(110);
|
||||
} else {
|
||||
g.hovered = base.darker(108);
|
||||
g.pressed = base.darker(120);
|
||||
g.checked = base.darker(112);
|
||||
g.focused = base.darker(105);
|
||||
}
|
||||
return g;
|
||||
}
|
||||
|
||||
Theme Theme::fromChrome(const QHash<QString, QColor> &v, bool dark)
|
||||
{
|
||||
Theme t;
|
||||
t.dark = dark;
|
||||
t.background = deriveGroup(v.value("palette/background"), dark);
|
||||
t.panel = deriveGroup(v.value("palette/panel"), dark);
|
||||
t.surface = deriveGroup(v.value("palette/surface"), dark);
|
||||
t.border = deriveGroup(v.value("palette/border"), dark);
|
||||
t.text = deriveGroup(v.value("palette/text"), dark);
|
||||
t.mutedText = deriveGroup(v.value("palette/mutedText"), dark);
|
||||
t.accent = deriveGroup(v.value("palette/accent"), dark);
|
||||
t.accentText = deriveGroup(v.value("palette/accentText"), dark);
|
||||
|
||||
const QColor muted = v.value("palette/mutedText");
|
||||
t.text.disabled = muted;
|
||||
t.accentText.disabled = muted;
|
||||
t.surface.disabled = t.surface.normal;
|
||||
t.border.disabled = muted;
|
||||
|
||||
const QColor face = t.surface.normal;
|
||||
t.bevelHighlight = face.lighter(160);
|
||||
t.bevelLight = face.lighter(115);
|
||||
t.bevelDark = face.darker(160);
|
||||
t.bevelShadow = face.darker(320);
|
||||
|
||||
t.tooltipBase = t.surface.normal;
|
||||
t.tooltipText = t.text.normal;
|
||||
return t;
|
||||
}
|
||||
|
||||
Theme Theme::loadBuiltin(const QString &name)
|
||||
{
|
||||
const QString path = QStringLiteral(":/themes/engine/") + name + QStringLiteral(".theme");
|
||||
if (QFile::exists(path)) {
|
||||
return load(path, true);
|
||||
}
|
||||
QHash<QString, QColor> v
|
||||
= {{"palette/background", QColor(0x31, 0x36, 0x3b)},
|
||||
{"palette/panel", QColor(0x23, 0x26, 0x29)},
|
||||
{"palette/surface", QColor(0x31, 0x36, 0x3b)},
|
||||
{"palette/border", QColor(0x4f, 0x56, 0x5c)},
|
||||
{"palette/text", QColor(0xef, 0xf0, 0xf1)},
|
||||
{"palette/mutedText", QColor(0x80, 0x80, 0x80)},
|
||||
{"palette/accent", QColor(0x3d, 0xae, 0xe9)},
|
||||
{"palette/accentText", QColor(0xff, 0xff, 0xff)}};
|
||||
Theme t = fromChrome(v, true);
|
||||
t.name = name;
|
||||
return t;
|
||||
}
|
||||
|
||||
Theme Theme::fromPalette(const QPalette &p, bool dark)
|
||||
{
|
||||
QHash<QString, QColor> v
|
||||
= {{"palette/background", p.color(QPalette::Active, QPalette::Window)},
|
||||
{"palette/panel", p.color(QPalette::Active, QPalette::Base)},
|
||||
{"palette/surface", p.color(QPalette::Active, QPalette::Button)},
|
||||
{"palette/border", p.color(QPalette::Active, QPalette::Mid)},
|
||||
{"palette/text", p.color(QPalette::Active, QPalette::WindowText)},
|
||||
{"palette/mutedText", p.color(QPalette::Disabled, QPalette::WindowText)},
|
||||
{"palette/accent", p.color(QPalette::Active, QPalette::Highlight)},
|
||||
{"palette/accentText", p.color(QPalette::Active, QPalette::HighlightedText)}};
|
||||
Theme t = fromChrome(v, dark);
|
||||
t.name = QStringLiteral("Native");
|
||||
return t;
|
||||
}
|
||||
|
||||
Theme Theme::iaitoDefault()
|
||||
{
|
||||
if (qApp) {
|
||||
const QPalette p = qApp->palette();
|
||||
const bool dark = p.color(QPalette::Active, QPalette::Base).lightnessF()
|
||||
< p.color(QPalette::Active, QPalette::WindowText).lightnessF();
|
||||
return fromPalette(p, dark);
|
||||
}
|
||||
return loadBuiltin(QStringLiteral("Dark"));
|
||||
}
|
||||
|
||||
Theme Theme::builtin(const QString &name)
|
||||
{
|
||||
return loadBuiltin(name);
|
||||
}
|
||||
|
||||
QHash<QString, QColor> Theme::toChrome() const
|
||||
{
|
||||
return {
|
||||
{"palette/background", background.normal},
|
||||
{"palette/panel", panel.normal},
|
||||
{"palette/surface", surface.normal},
|
||||
{"palette/border", border.normal},
|
||||
{"palette/text", text.normal},
|
||||
{"palette/mutedText", mutedText.normal},
|
||||
{"palette/accent", accent.normal},
|
||||
{"palette/accentText", accentText.normal}};
|
||||
}
|
||||
|
||||
QPalette Theme::toPalette(const QPalette &base) const
|
||||
{
|
||||
QPalette p = base;
|
||||
struct Map
|
||||
{
|
||||
QPalette::ColorRole role;
|
||||
const ColorGroup *group;
|
||||
};
|
||||
const Map table[]
|
||||
= {{QPalette::Window, &background},
|
||||
{QPalette::WindowText, &text},
|
||||
{QPalette::Base, &panel},
|
||||
{QPalette::AlternateBase, &panel},
|
||||
{QPalette::Text, &text},
|
||||
{QPalette::Button, &surface},
|
||||
{QPalette::ButtonText, &text},
|
||||
{QPalette::Highlight, &accent},
|
||||
{QPalette::HighlightedText, &accentText},
|
||||
{QPalette::PlaceholderText, &mutedText}};
|
||||
for (const Map &m : table) {
|
||||
p.setColor(QPalette::Active, m.role, m.group->normal);
|
||||
p.setColor(QPalette::Inactive, m.role, m.group->normal);
|
||||
p.setColor(QPalette::Disabled, m.role, m.group->disabled);
|
||||
}
|
||||
for (QPalette::ColorGroup g : {QPalette::Active, QPalette::Inactive, QPalette::Disabled}) {
|
||||
p.setColor(g, QPalette::ToolTipBase, tooltipBase);
|
||||
p.setColor(g, QPalette::ToolTipText, tooltipText);
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
const struct
|
||||
{
|
||||
const char *key;
|
||||
int ThemeMetrics::*field;
|
||||
} kMetricFields[] = {
|
||||
{"metrics/radius", &ThemeMetrics::borderRadius},
|
||||
{"metrics/lineWidth", &ThemeMetrics::borderWidth},
|
||||
{"metrics/focusWidth", &ThemeMetrics::focusWidth},
|
||||
{"metrics/spacing", &ThemeMetrics::spacing},
|
||||
{"metrics/iconSize", &ThemeMetrics::iconSize},
|
||||
{"metrics/sliderGroove", &ThemeMetrics::sliderGrooveHeight},
|
||||
{"controls/height", &ThemeMetrics::controlHeight},
|
||||
{"controls/checkbox", &ThemeMetrics::checkBoxSize},
|
||||
{"controls/checkboxRadius", &ThemeMetrics::checkBoxRadius},
|
||||
{"controls/menuPadding", &ThemeMetrics::menuItemPadding},
|
||||
{"scrollbar/thickness", &ThemeMetrics::scrollBarThickness},
|
||||
{"tabs/border", &ThemeMetrics::tabBorderWidth},
|
||||
{"tabs/indicator", &ThemeMetrics::tabIndicatorWidth},
|
||||
};
|
||||
|
||||
const struct
|
||||
{
|
||||
const char *key;
|
||||
QColor Theme::*field;
|
||||
} kOverrideColors[] = {
|
||||
{"bevel/highlight", &Theme::bevelHighlight},
|
||||
{"bevel/light", &Theme::bevelLight},
|
||||
{"bevel/dark", &Theme::bevelDark},
|
||||
{"bevel/shadow", &Theme::bevelShadow},
|
||||
{"tooltip/base", &Theme::tooltipBase},
|
||||
{"tooltip/text", &Theme::tooltipText},
|
||||
};
|
||||
|
||||
QString skinToString(Skin s)
|
||||
{
|
||||
return s == Skin::Bevel ? QStringLiteral("bevel") : QStringLiteral("modern");
|
||||
}
|
||||
|
||||
Skin skinFromString(const QString &s)
|
||||
{
|
||||
return s == QLatin1String("bevel") ? Skin::Bevel : Skin::Modern;
|
||||
}
|
||||
|
||||
QString chromeToString(ChromeStyle c)
|
||||
{
|
||||
switch (c) {
|
||||
case ChromeStyle::Accent:
|
||||
return QStringLiteral("accent");
|
||||
case ChromeStyle::AccentTabs:
|
||||
return QStringLiteral("accent-tabs");
|
||||
case ChromeStyle::Flat:
|
||||
return QStringLiteral("flat");
|
||||
}
|
||||
return QStringLiteral("flat");
|
||||
}
|
||||
|
||||
ChromeStyle chromeFromString(const QString &s)
|
||||
{
|
||||
if (s == QLatin1String("accent")) {
|
||||
return ChromeStyle::Accent;
|
||||
}
|
||||
if (s == QLatin1String("accent-tabs")) {
|
||||
return ChromeStyle::AccentTabs;
|
||||
}
|
||||
return ChromeStyle::Flat;
|
||||
}
|
||||
|
||||
QString hintingToString(int h)
|
||||
{
|
||||
switch (h) {
|
||||
case QFont::PreferNoHinting:
|
||||
return QStringLiteral("none");
|
||||
case QFont::PreferVerticalHinting:
|
||||
return QStringLiteral("vertical");
|
||||
case QFont::PreferFullHinting:
|
||||
return QStringLiteral("full");
|
||||
default:
|
||||
return QStringLiteral("default");
|
||||
}
|
||||
}
|
||||
|
||||
QFont::HintingPreference hintingFromString(const QString &s)
|
||||
{
|
||||
if (s == QLatin1String("none"))
|
||||
return QFont::PreferNoHinting;
|
||||
if (s == QLatin1String("vertical"))
|
||||
return QFont::PreferVerticalHinting;
|
||||
if (s == QLatin1String("full"))
|
||||
return QFont::PreferFullHinting;
|
||||
if (s == QLatin1String("default"))
|
||||
return QFont::PreferDefaultHinting;
|
||||
bool ok = false;
|
||||
const int v = s.toInt(&ok);
|
||||
return ok ? QFont::HintingPreference(v) : QFont::PreferDefaultHinting;
|
||||
}
|
||||
|
||||
QString colorString(const QColor &c)
|
||||
{
|
||||
return c.alpha() == 255 ? c.name(QColor::HexRgb) : c.name(QColor::HexArgb);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void Theme::applyIni(const QString &path)
|
||||
{
|
||||
QSettings s(path, QSettings::IniFormat);
|
||||
for (const auto &e : kMetricFields) {
|
||||
metrics.*e.field = s.value(e.key, metrics.*e.field).toInt();
|
||||
}
|
||||
for (const auto &e : kOverrideColors) {
|
||||
const QColor c(s.value(e.key).toString());
|
||||
if (c.isValid()) {
|
||||
this->*e.field = c;
|
||||
}
|
||||
}
|
||||
skin = skinFromString(s.value("meta/skin", skinToString(skin)).toString());
|
||||
chromeStyle = chromeFromString(s.value("meta/chrome", chromeToString(chromeStyle)).toString());
|
||||
name = s.value("meta/name", name).toString();
|
||||
|
||||
const QString family = s.value("font/family").toString();
|
||||
if (!family.isEmpty()) {
|
||||
QFont f(family);
|
||||
const QString size = s.value("font/size").toString();
|
||||
if (size.endsWith(QLatin1String("px"))) {
|
||||
f.setPixelSize(size.left(size.size() - 2).toInt());
|
||||
} else if (size.endsWith(QLatin1String("pt"))) {
|
||||
f.setPointSizeF(size.left(size.size() - 2).toDouble());
|
||||
}
|
||||
f.setHintingPreference(hintingFromString(s.value("font/hinting").toString()));
|
||||
fonts.base = f;
|
||||
fonts.custom = true;
|
||||
}
|
||||
}
|
||||
|
||||
Theme Theme::load(const QString &path, bool dark)
|
||||
{
|
||||
QSettings s(path, QSettings::IniFormat);
|
||||
QHash<QString, QColor> vars = Theme().toChrome();
|
||||
const QStringList keys = vars.keys();
|
||||
for (const QString &key : keys) {
|
||||
const QColor c(s.value(key).toString());
|
||||
if (c.isValid()) {
|
||||
vars[key] = c;
|
||||
}
|
||||
}
|
||||
Theme t = fromChrome(vars, s.value("meta/dark", dark).toBool());
|
||||
t.applyIni(path);
|
||||
return t;
|
||||
}
|
||||
|
||||
void Theme::save(const QString &path) const
|
||||
{
|
||||
QSettings s(path, QSettings::IniFormat);
|
||||
const QHash<QString, QColor> chrome = toChrome();
|
||||
for (auto it = chrome.constBegin(); it != chrome.constEnd(); ++it) {
|
||||
s.setValue(it.key(), colorString(it.value()));
|
||||
}
|
||||
s.setValue("bevel/highlight", colorString(bevelHighlight));
|
||||
s.setValue("bevel/light", colorString(bevelLight));
|
||||
s.setValue("bevel/dark", colorString(bevelDark));
|
||||
s.setValue("bevel/shadow", colorString(bevelShadow));
|
||||
s.setValue("tooltip/base", colorString(tooltipBase));
|
||||
s.setValue("tooltip/text", colorString(tooltipText));
|
||||
for (const auto &e : kMetricFields) {
|
||||
s.setValue(e.key, metrics.*e.field);
|
||||
}
|
||||
s.setValue("meta/name", name);
|
||||
s.setValue("meta/dark", dark);
|
||||
s.setValue("meta/skin", skinToString(skin));
|
||||
s.setValue("meta/chrome", chromeToString(chromeStyle));
|
||||
if (fonts.custom) {
|
||||
s.setValue("font/family", fonts.base.family());
|
||||
s.setValue(
|
||||
"font/size",
|
||||
fonts.base.pixelSize() > 0
|
||||
? QString::number(fonts.base.pixelSize()) + QStringLiteral("px")
|
||||
: QString::number(fonts.base.pointSizeF()) + QStringLiteral("pt"));
|
||||
s.setValue("font/hinting", hintingToString(fonts.base.hintingPreference()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
#ifndef IAITO_THEME_H
|
||||
#define IAITO_THEME_H
|
||||
|
||||
#include <QColor>
|
||||
#include <QFont>
|
||||
#include <QHash>
|
||||
#include <QPalette>
|
||||
#include <QString>
|
||||
#include <QStyle>
|
||||
|
||||
struct ColorGroup
|
||||
{
|
||||
QColor normal;
|
||||
QColor hovered;
|
||||
QColor pressed;
|
||||
QColor disabled;
|
||||
QColor checked;
|
||||
QColor focused;
|
||||
|
||||
QColor forState(QStyle::State state) const;
|
||||
};
|
||||
|
||||
struct ThemeMetrics
|
||||
{
|
||||
int borderRadius = 4;
|
||||
int checkBoxRadius = 4;
|
||||
int borderWidth = 1;
|
||||
int focusWidth = 2;
|
||||
int spacing = 6;
|
||||
int menuItemPadding = 6;
|
||||
int iconSize = 16;
|
||||
int controlHeight = 24;
|
||||
int checkBoxSize = 16;
|
||||
int scrollBarThickness = 12;
|
||||
int sliderGrooveHeight = 4;
|
||||
int tabBorderWidth = 0;
|
||||
int tabIndicatorWidth = 2;
|
||||
};
|
||||
|
||||
struct ThemeFonts
|
||||
{
|
||||
QFont base;
|
||||
bool custom = false;
|
||||
};
|
||||
|
||||
enum class ChromeStyle {
|
||||
Flat,
|
||||
Accent,
|
||||
AccentTabs,
|
||||
};
|
||||
|
||||
enum class Skin {
|
||||
Modern,
|
||||
Bevel,
|
||||
};
|
||||
|
||||
class Theme
|
||||
{
|
||||
public:
|
||||
ColorGroup background;
|
||||
ColorGroup panel;
|
||||
ColorGroup surface;
|
||||
ColorGroup border;
|
||||
ColorGroup text;
|
||||
ColorGroup mutedText;
|
||||
ColorGroup accent;
|
||||
ColorGroup accentText;
|
||||
|
||||
QColor bevelHighlight;
|
||||
QColor bevelLight;
|
||||
QColor bevelDark;
|
||||
QColor bevelShadow;
|
||||
|
||||
QColor tooltipBase;
|
||||
QColor tooltipText;
|
||||
|
||||
ThemeMetrics metrics;
|
||||
ThemeFonts fonts;
|
||||
ChromeStyle chromeStyle = ChromeStyle::Flat;
|
||||
Skin skin = Skin::Modern;
|
||||
bool dark = true;
|
||||
QString name;
|
||||
|
||||
static Theme fromChrome(const QHash<QString, QColor> &vars, bool dark);
|
||||
static Theme fromPalette(const QPalette &palette, bool dark);
|
||||
static Theme loadBuiltin(const QString &name);
|
||||
static Theme iaitoDefault();
|
||||
static Theme builtin(const QString &name);
|
||||
QHash<QString, QColor> toChrome() const;
|
||||
|
||||
QPalette toPalette(const QPalette &base) const;
|
||||
|
||||
static Theme load(const QString &path, bool dark);
|
||||
void save(const QString &path) const;
|
||||
void applyIni(const QString &path);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,41 @@
|
||||
#ifndef DOCKBACKEND_H
|
||||
#define DOCKBACKEND_H
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QList>
|
||||
#include <Qt>
|
||||
|
||||
class QDockWidget;
|
||||
class QTabBar;
|
||||
|
||||
// Abstract realization backend for the dock layout model. The DockManager owns
|
||||
// the structure (the node tree); the backend turns structural intent into a
|
||||
// concrete widget arrangement. QtDockBackend realizes it onto QMainWindow's
|
||||
// native docking; a future backend could use QSplitter/QStackedWidget instead
|
||||
// without touching the model, registry, drag, or MainWindow.
|
||||
class DockBackend
|
||||
{
|
||||
public:
|
||||
virtual ~DockBackend() = default;
|
||||
|
||||
virtual void addDock(QDockWidget *dock, Qt::DockWidgetArea area) = 0;
|
||||
virtual void splitDock(QDockWidget *first, QDockWidget *second, Qt::Orientation orientation) = 0;
|
||||
virtual void tabifyDock(QDockWidget *first, QDockWidget *second) = 0;
|
||||
virtual void removeDock(QDockWidget *dock) = 0;
|
||||
virtual void setDockFloating(QDockWidget *dock, bool floating) = 0;
|
||||
|
||||
virtual QList<QDockWidget *> tabifiedWith(QDockWidget *dock) const = 0;
|
||||
virtual Qt::DockWidgetArea areaOf(QDockWidget *dock) const = 0;
|
||||
virtual void resizeDocksTo(
|
||||
const QList<QDockWidget *> &docks, const QList<int> &sizes, Qt::Orientation orientation)
|
||||
= 0;
|
||||
|
||||
virtual QList<QTabBar *> dockTabBars() const = 0;
|
||||
virtual void setTabsOnTop(bool onTop) = 0;
|
||||
virtual void setGroupedDragging(bool enabled) = 0;
|
||||
|
||||
virtual QByteArray saveState() const = 0;
|
||||
virtual bool restoreState(const QByteArray &state) = 0;
|
||||
};
|
||||
|
||||
#endif // DOCKBACKEND_H
|
||||
@@ -0,0 +1,153 @@
|
||||
#ifndef DOCKMANAGER_H
|
||||
#define DOCKMANAGER_H
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QList>
|
||||
#include <QMap>
|
||||
#include <QObject>
|
||||
#include <QPoint>
|
||||
#include <QPointer>
|
||||
#include <QRect>
|
||||
#include <QSize>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
|
||||
class MainWindow;
|
||||
class DockBackend;
|
||||
class IaitoDockWidget;
|
||||
class QDockWidget;
|
||||
class QTabBar;
|
||||
class QToolButton;
|
||||
class QMouseEvent;
|
||||
class QEvent;
|
||||
class QWidget;
|
||||
|
||||
// Where a dragged panel would land relative to the panel under the cursor.
|
||||
enum class DockDropKind {
|
||||
None,
|
||||
Float,
|
||||
Tabify,
|
||||
SplitLeft,
|
||||
SplitRight,
|
||||
SplitTop,
|
||||
SplitBottom,
|
||||
};
|
||||
|
||||
struct DockDropPlan
|
||||
{
|
||||
IaitoDockWidget *target = nullptr;
|
||||
DockDropKind kind = DockDropKind::None;
|
||||
IaitoDockWidget *previewHost = nullptr;
|
||||
QRect region; // previewHost-local coords, for the preview overlay
|
||||
};
|
||||
|
||||
// Owns the live set of dock panels and the homegrown chrome/drag layer that
|
||||
// used to live inline in MainWindow. This is the seam through which all docking
|
||||
// behavior is funneled, so the structure can later be backed by an explicit
|
||||
// layout model without touching MainWindow.
|
||||
class DockManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit DockManager(MainWindow *mainWindow);
|
||||
~DockManager() override;
|
||||
|
||||
const QList<IaitoDockWidget *> &docks() const { return dockWidgets; }
|
||||
const QList<IaitoDockWidget *> &pluginDockWidgets() const { return pluginDocks; }
|
||||
void addWidget(IaitoDockWidget *widget);
|
||||
void removeWidget(IaitoDockWidget *widget);
|
||||
void addPluginDock(IaitoDockWidget *widget) { pluginDocks.push_back(widget); }
|
||||
QString getUniqueObjectName(const QString &widgetType) const;
|
||||
QStringList getPanelNames() const;
|
||||
bool focusPanelByName(const QString &name);
|
||||
|
||||
QMap<QString, std::function<IaitoDockWidget *(MainWindow *)>> &constructorMap()
|
||||
{
|
||||
return widgetTypeToConstructorMap;
|
||||
}
|
||||
|
||||
void applyDockPanelChrome();
|
||||
void updateDockTabBars();
|
||||
void requestUpdateTabBars();
|
||||
bool closeCurrentTab();
|
||||
bool selectCurrentPanelTab(int index);
|
||||
bool focusNextFrame(bool forward);
|
||||
bool toggleCurrentPanelMaximized();
|
||||
void restoreMaximizedPanel();
|
||||
QByteArray saveState() const;
|
||||
|
||||
bool handleEvent(QObject *watched, QEvent *event);
|
||||
|
||||
signals:
|
||||
void panelAdded(IaitoDockWidget *widget);
|
||||
void panelRemoved(IaitoDockWidget *widget);
|
||||
// Emitted whenever the docked arrangement may have changed (add/remove,
|
||||
// visibility, float/unfloat) so the owner can re-check layout invariants.
|
||||
void layoutMutated();
|
||||
|
||||
private:
|
||||
void setActiveDock(IaitoDockWidget *dock);
|
||||
void requestUpdateActiveFrame();
|
||||
void updateActiveFrame();
|
||||
IaitoDockWidget *dockForWidget(QWidget *widget) const;
|
||||
void configureDockWidget(QDockWidget *dock);
|
||||
bool updateEmptyDockTabBar(QTabBar *tabBar);
|
||||
bool configureDockTabBar(QTabBar *tabBar);
|
||||
bool isMainDockTabBar(QTabBar *tabBar) const;
|
||||
void updateDockTabCloseButtons(QTabBar *tabBar, int hoveredIndex = -1);
|
||||
QToolButton *dockTabCloseButton(QTabBar *tabBar);
|
||||
void closeDockTab(QTabBar *tabBar, int index);
|
||||
void showDockContextMenu(QWidget *parent, IaitoDockWidget *dock, const QPoint &globalPos);
|
||||
void detachDockTab(IaitoDockWidget *dock, const QPoint &globalPos);
|
||||
void placeDockTab(IaitoDockWidget *dock, DockDropKind kind);
|
||||
void refreshNativeDockLayout();
|
||||
IaitoDockWidget *dockSplitReference(IaitoDockWidget *dock) const;
|
||||
bool dockHasOpenTab(IaitoDockWidget *dock) const;
|
||||
bool rebuildMacDockLayoutForSplit(
|
||||
IaitoDockWidget *dock, IaitoDockWidget *targetDock, Qt::Orientation orientation);
|
||||
bool splitDockRelativeTo(
|
||||
IaitoDockWidget *dock,
|
||||
IaitoDockWidget *targetDock,
|
||||
Qt::Orientation orientation,
|
||||
IaitoDockWidget *restoreTabHost);
|
||||
IaitoDockWidget *dockForDockTab(QTabBar *tabBar, int index) const;
|
||||
IaitoDockWidget *dockForDockDragHandle(QWidget *handle) const;
|
||||
bool showDockHandleContextMenu(QWidget *handle, const QPoint &globalPos);
|
||||
bool maybeStartDockTabDrag(QTabBar *tabBar, QMouseEvent *event);
|
||||
bool maybeStartDockHandleDrag(QWidget *handle, QMouseEvent *event);
|
||||
void startDockWidgetDrag(IaitoDockWidget *dock, const QPoint &globalPos, const QPoint &offset);
|
||||
bool updateDockTabDrag(QMouseEvent *event);
|
||||
DockDropPlan computeDropPlan(const QPoint &globalPos) const;
|
||||
void updateDropOverlay(const QPoint &globalPos);
|
||||
void finishDockTabDrag(const QPoint &globalPos);
|
||||
void clearDockDrag();
|
||||
|
||||
MainWindow *mainWindow;
|
||||
DockBackend *backend;
|
||||
|
||||
QList<IaitoDockWidget *> dockWidgets;
|
||||
QList<IaitoDockWidget *> pluginDocks;
|
||||
QMap<QString, std::function<IaitoDockWidget *(MainWindow *)>> widgetTypeToConstructorMap;
|
||||
|
||||
QPointer<IaitoDockWidget> activeDock;
|
||||
QList<QPointer<QWidget>> activeFrameBorders;
|
||||
bool activeFrameUpdateScheduled = false;
|
||||
QPointer<IaitoDockWidget> maximizedDock;
|
||||
QPointer<QWidget> maximizedFocusWidget;
|
||||
QByteArray maximizedLayoutState;
|
||||
QSize maximizedDockMaximumSize;
|
||||
|
||||
QPointer<QTabBar> dockDragTabBar;
|
||||
QPointer<QWidget> dockDragHandle;
|
||||
QPointer<IaitoDockWidget> dockDragWidget;
|
||||
QPointer<QWidget> dropOverlay;
|
||||
QPoint dockDragStartGlobalPos;
|
||||
QPoint dockDragOffset;
|
||||
bool dockTabDragActive = false;
|
||||
bool dockRelayoutInProgress = false;
|
||||
bool updateScheduled = false;
|
||||
};
|
||||
|
||||
#endif // DOCKMANAGER_H
|
||||
@@ -1,8 +1,73 @@
|
||||
#include "Iaito.h"
|
||||
#include "IaitoApplication.h"
|
||||
#include "common/Radare2Compat.h"
|
||||
#include <QFileDialog>
|
||||
#include <QMessageBox>
|
||||
|
||||
static MainWindow *uiaitoMainWindow()
|
||||
{
|
||||
auto *app = qobject_cast<IaitoApplication *>(qApp);
|
||||
return app ? app->getMainWindow() : nullptr;
|
||||
}
|
||||
|
||||
static QString uiaitoStringArgument(const char *input)
|
||||
{
|
||||
QString arg = QString::fromUtf8(input).trimmed();
|
||||
if (arg.size() >= 2) {
|
||||
const QChar first = arg.at(0);
|
||||
const QChar last = arg.at(arg.size() - 1);
|
||||
if ((first == QLatin1Char('"') && last == QLatin1Char('"'))
|
||||
|| (first == QLatin1Char('\'') && last == QLatin1Char('\''))) {
|
||||
arg = arg.mid(1, arg.size() - 2).trimmed();
|
||||
}
|
||||
}
|
||||
return arg;
|
||||
}
|
||||
|
||||
static bool uiaitoParseRangeArgument(const char *input, RVA *start, RVA *end, QString *error)
|
||||
{
|
||||
const QString args = QString::fromUtf8(input).simplified();
|
||||
const QStringList parts = args.split(QLatin1Char(' '), Qt::SkipEmptyParts);
|
||||
if (parts.isEmpty() || parts.size() > 2) {
|
||||
*error = QStringLiteral("Usage: uis [address] ([length])");
|
||||
return false;
|
||||
}
|
||||
|
||||
const RVA address = Core()->math(parts.at(0));
|
||||
if (address == RVA_INVALID) {
|
||||
*error = QStringLiteral("Invalid address");
|
||||
return false;
|
||||
}
|
||||
|
||||
const RVA length = parts.size() == 2 ? Core()->math(parts.at(1)) : 1;
|
||||
if (length == 0 || length == RVA_INVALID) {
|
||||
*error = QStringLiteral("Invalid length");
|
||||
return false;
|
||||
}
|
||||
if (length - 1 > RVA_MAX - address) {
|
||||
*error = QStringLiteral("Selection range overflows");
|
||||
return false;
|
||||
}
|
||||
|
||||
*start = address;
|
||||
*end = address + length - 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void uiaitoPrintUsage(RCore *core)
|
||||
{
|
||||
static const char *usage = "Usage: ui[..] [..args] - uiaito interactions\n"
|
||||
"| ui [message] - show popup dialog with given message\n"
|
||||
"| uid ([path]) - select directory and print it\n"
|
||||
"| uif ([path]) - select file and print it\n"
|
||||
"| uig [offset] - goto offset in UI\n"
|
||||
"| uip ([name]) - list panels or focus panel by name\n"
|
||||
"| uir - refresh UI contents\n"
|
||||
"| uis [addr] ([len]) - select byte range in UI\n";
|
||||
|
||||
r_cons_printf(core->cons, "%s", usage);
|
||||
}
|
||||
|
||||
static bool r2plugin_ui_call(RCorePluginSession *cps, const char *input)
|
||||
{
|
||||
RCore *core = cps->core;
|
||||
@@ -42,14 +107,77 @@ static bool r2plugin_ui_call(RCorePluginSession *cps, const char *input)
|
||||
r_cons_printf(core->cons, "%s\n", filename.toUtf8().constData());
|
||||
}
|
||||
} break;
|
||||
case 'g': {
|
||||
MainWindow *mainWindow = uiaitoMainWindow();
|
||||
if (!mainWindow) {
|
||||
r_core_return_code(core, 1);
|
||||
r_cons_printf(core->cons, "No iaito main window available\n");
|
||||
break;
|
||||
}
|
||||
|
||||
const QString offset = uiaitoStringArgument(input + 3);
|
||||
if (offset.isEmpty()) {
|
||||
r_core_return_code(core, 1);
|
||||
r_cons_printf(core->cons, "Usage: uig [offset]\n");
|
||||
break;
|
||||
}
|
||||
|
||||
mainWindow->gotoOffset(offset);
|
||||
r_core_return_code(core, 0);
|
||||
} break;
|
||||
case 's': {
|
||||
MainWindow *mainWindow = uiaitoMainWindow();
|
||||
if (!mainWindow) {
|
||||
r_core_return_code(core, 1);
|
||||
r_cons_printf(core->cons, "No iaito main window available\n");
|
||||
break;
|
||||
}
|
||||
|
||||
RVA start = RVA_INVALID;
|
||||
RVA end = RVA_INVALID;
|
||||
QString error;
|
||||
if (!uiaitoParseRangeArgument(input + 3, &start, &end, &error)) {
|
||||
r_core_return_code(core, 1);
|
||||
r_cons_printf(core->cons, "%s\n", error.toUtf8().constData());
|
||||
break;
|
||||
}
|
||||
|
||||
mainWindow->gotoOffset(RAddressString(start));
|
||||
Core()->setAddressRangeSelection(start, end);
|
||||
r_core_return_code(core, 0);
|
||||
} break;
|
||||
case 'r': {
|
||||
Core()->triggerRefreshAll();
|
||||
r_core_return_code(core, 0);
|
||||
} break;
|
||||
case 'p': {
|
||||
MainWindow *mainWindow = uiaitoMainWindow();
|
||||
if (!mainWindow) {
|
||||
r_core_return_code(core, 1);
|
||||
r_cons_printf(core->cons, "No iaito main window available\n");
|
||||
break;
|
||||
}
|
||||
|
||||
const QString panelName = uiaitoStringArgument(input + 3);
|
||||
if (panelName.isEmpty()) {
|
||||
const QStringList panelNames = mainWindow->getPanelNames();
|
||||
for (const QString &name : panelNames) {
|
||||
r_cons_printf(core->cons, "%s\n", name.toUtf8().constData());
|
||||
}
|
||||
r_core_return_code(core, 0);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!mainWindow->focusPanelByName(panelName)) {
|
||||
r_core_return_code(core, 1);
|
||||
r_cons_printf(core->cons, "Panel not found: %s\n", panelName.toUtf8().constData());
|
||||
break;
|
||||
}
|
||||
|
||||
r_core_return_code(core, 0);
|
||||
} break;
|
||||
default:
|
||||
r_cons_printf(core->cons, "Usage: ui[..] [..args] - uiaito interactions\n");
|
||||
r_cons_printf(
|
||||
core->cons,
|
||||
"| ui [message] - show popup dialog with given "
|
||||
"message\n");
|
||||
r_cons_printf(core->cons, "| uid ([path]) - select directory and print it\n");
|
||||
r_cons_printf(core->cons, "| uif ([path]) - select file and print it\n");
|
||||
uiaitoPrintUsage(core);
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#define MONOTHREAD 1
|
||||
|
||||
#include "common/BasicInstructionHighlighter.h"
|
||||
#include "common/ExceptionInfo.h"
|
||||
#include "core/IaitoCommon.h"
|
||||
#include "core/IaitoDescriptions.h"
|
||||
|
||||
@@ -324,6 +325,17 @@ public:
|
||||
void seekPrev();
|
||||
void seekNext();
|
||||
void updateSeek();
|
||||
|
||||
/**
|
||||
* @brief Set the globally synchronized address range selection.
|
||||
* The range is inclusive. Passing an invalid or reversed range clears it.
|
||||
*/
|
||||
void setAddressRangeSelection(RVA start, RVA end);
|
||||
void clearAddressRangeSelection();
|
||||
bool hasAddressRangeSelection() const { return addressRangeSelectionStart != RVA_INVALID; }
|
||||
RVA getAddressRangeSelectionStart() const { return addressRangeSelectionStart; }
|
||||
RVA getAddressRangeSelectionEnd() const { return addressRangeSelectionEnd; }
|
||||
|
||||
/**
|
||||
* @brief Raise a memory widget showing current offset, prefer last active
|
||||
* memory widget.
|
||||
@@ -346,6 +358,7 @@ public:
|
||||
|
||||
/* Math functions */
|
||||
ut64 math(const QString &expr);
|
||||
bool tryMath(const QString &expr, ut64 &value);
|
||||
ut64 num(const QString &expr);
|
||||
QString itoa(ut64 num, int rdx = 16);
|
||||
|
||||
@@ -375,7 +388,9 @@ public:
|
||||
|
||||
/* Assembly\Hexdump related methods */
|
||||
QByteArray assemble(const QString &code);
|
||||
QByteArray assembleAt(const QString &code, RVA address);
|
||||
QString disassemble(const QByteArray &data);
|
||||
QString disassembleAt(const QByteArray &data, RVA address);
|
||||
QString disassembleSingleInstruction(RVA addr);
|
||||
QList<DisassemblyLine> disassembleLines(RVA offset, int lines);
|
||||
|
||||
@@ -450,6 +465,7 @@ public:
|
||||
void suspendDebug();
|
||||
|
||||
private:
|
||||
int exceptionJsonSupport = -1;
|
||||
void finishStopDebug();
|
||||
|
||||
public:
|
||||
@@ -539,6 +555,8 @@ public:
|
||||
void loadPDB(const QString &file);
|
||||
|
||||
QByteArray ioRead(RVA addr, int len);
|
||||
QString getCommandAnnotation(RVA address, RVA *size = nullptr);
|
||||
void setCommandAnnotation(RVA address, RVA size, const QString &command);
|
||||
|
||||
QList<RVA> getSeekHistory();
|
||||
|
||||
@@ -551,6 +569,8 @@ public:
|
||||
void openProject(const QString &name);
|
||||
void saveProject(const QString &name);
|
||||
void deleteProject(const QString &name);
|
||||
bool isProjectDirty();
|
||||
void markProjectClean();
|
||||
static bool isProjectNameValid(const QString &name);
|
||||
|
||||
/* Widgets */
|
||||
@@ -564,6 +584,7 @@ public:
|
||||
QList<RDebugPluginDescription> getRDebugPluginDescriptions();
|
||||
QList<RMutaPluginDescription> getRMutaPluginDescriptions();
|
||||
QList<FunctionDescription> getAllFunctions();
|
||||
QList<ExceptionRegion> getExceptionRegions(RVA function = RVA_INVALID);
|
||||
QList<ImportDescription> getAllImports();
|
||||
QList<ExportDescription> getAllExports();
|
||||
QList<SymbolDescription> getAllSymbols();
|
||||
@@ -574,12 +595,24 @@ public:
|
||||
QList<StringDescription> getAllStrings();
|
||||
QList<FlagspaceDescription> getAllFlagspaces();
|
||||
QList<FlagDescription> getAllFlags(QString flagspace = QString());
|
||||
QList<GlobalVariableDescription> getAllGlobalVariables();
|
||||
bool setGlobalVariable(
|
||||
RVA address,
|
||||
const QString &name,
|
||||
const QString &type,
|
||||
const QString &previousName = QString(),
|
||||
QString *errorMessage = nullptr);
|
||||
bool deleteGlobalVariable(RVA address, const QString &name);
|
||||
QList<SectionDescription> getAllSections();
|
||||
QList<SegmentDescription> getAllSegments();
|
||||
QList<EntrypointDescription> getAllEntrypoint();
|
||||
QList<BinClassDescription> getAllClassesFromBin();
|
||||
QList<BinClassDescription> getAllClassesFromFlags();
|
||||
QList<ResourcesDescription> getAllResources();
|
||||
bool dumpResource(
|
||||
const ResourcesDescription &resource,
|
||||
const QString &fileName,
|
||||
QString *errorMessage = nullptr);
|
||||
QList<VTableDescription> getAllVTables();
|
||||
|
||||
/**
|
||||
@@ -788,6 +821,12 @@ signals:
|
||||
*/
|
||||
void seekChanged(RVA offset);
|
||||
|
||||
/**
|
||||
* @brief emitted when a synchronized address range selection changes.
|
||||
* The range is inclusive; RVA_INVALID, RVA_INVALID means no active range.
|
||||
*/
|
||||
void addressRangeSelectionChanged(RVA start, RVA end);
|
||||
|
||||
void toggleDebugView();
|
||||
|
||||
void newMessage(const QString &msg);
|
||||
@@ -818,6 +857,8 @@ private:
|
||||
QList<Decompiler *> decompilers;
|
||||
|
||||
bool emptyGraph = false;
|
||||
RVA addressRangeSelectionStart = RVA_INVALID;
|
||||
RVA addressRangeSelectionEnd = RVA_INVALID;
|
||||
BasicBlockHighlighter *bbHighlighter;
|
||||
bool iocache = false;
|
||||
BasicInstructionHighlighter biHighlighter;
|
||||
|
||||
@@ -22,6 +22,8 @@ struct FunctionDescription
|
||||
RVA nlocals = 0;
|
||||
QString calltype;
|
||||
QString name;
|
||||
QString color;
|
||||
QString pin;
|
||||
RVA edges = 0;
|
||||
RVA stackframe = 0;
|
||||
|
||||
@@ -96,6 +98,7 @@ struct SymbolDescription
|
||||
QString bind;
|
||||
QString type;
|
||||
QString name;
|
||||
QString demangledName;
|
||||
};
|
||||
|
||||
struct CommentDescription
|
||||
@@ -135,6 +138,14 @@ struct FlagDescription
|
||||
QString realname;
|
||||
};
|
||||
|
||||
struct GlobalVariableDescription
|
||||
{
|
||||
RVA address = RVA_INVALID;
|
||||
RVA size = 0;
|
||||
QString name;
|
||||
QString type;
|
||||
};
|
||||
|
||||
struct SectionDescription
|
||||
{
|
||||
RVA vaddr = 0;
|
||||
@@ -262,6 +273,7 @@ struct BinClassBaseClassDescription
|
||||
struct BinClassMethodDescription
|
||||
{
|
||||
QString name;
|
||||
QString mangledName;
|
||||
RVA addr = RVA_INVALID;
|
||||
st64 vtableOffset = -1;
|
||||
};
|
||||
@@ -308,10 +320,19 @@ struct ResourcesDescription
|
||||
{
|
||||
QString name;
|
||||
RVA vaddr = 0;
|
||||
ut64 index = 0;
|
||||
QString type;
|
||||
RVA paddr = RVA_INVALID;
|
||||
ut64 size = 0;
|
||||
QString lang;
|
||||
ut64 id = UT64_MAX;
|
||||
ut32 index = 0;
|
||||
ut32 typeId = UT32_MAX;
|
||||
ut32 languageId = 0;
|
||||
ut32 codepage = 0;
|
||||
bool named = false;
|
||||
bool hasExtendedMetadata = false;
|
||||
QString type;
|
||||
QString language;
|
||||
QString timestamp;
|
||||
QString origin;
|
||||
};
|
||||
|
||||
struct VTableDescription
|
||||
@@ -330,7 +351,9 @@ struct BlockDescription
|
||||
int comments = 0;
|
||||
int symbols = 0;
|
||||
int strings = 0;
|
||||
int blocks = 0;
|
||||
ut8 rwx = 0;
|
||||
QString color;
|
||||
};
|
||||
|
||||
struct BlockStatistics
|
||||
@@ -412,6 +435,7 @@ Q_DECLARE_METATYPE(RelocDescription)
|
||||
Q_DECLARE_METATYPE(StringDescription)
|
||||
Q_DECLARE_METATYPE(FlagspaceDescription)
|
||||
Q_DECLARE_METATYPE(FlagDescription)
|
||||
Q_DECLARE_METATYPE(GlobalVariableDescription)
|
||||
Q_DECLARE_METATYPE(XrefDescription)
|
||||
Q_DECLARE_METATYPE(EntrypointDescription)
|
||||
Q_DECLARE_METATYPE(RBinPluginDescription)
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
#include <QEvent>
|
||||
#include <QList>
|
||||
#include <QMainWindow>
|
||||
#include <QPointer>
|
||||
#include <QStringList>
|
||||
#include <QToolButton>
|
||||
|
||||
class IaitoCore;
|
||||
@@ -26,14 +28,18 @@ class AsciiHighlighter;
|
||||
class VisualNavbar;
|
||||
class FunctionsWidget;
|
||||
class ImportsWidget;
|
||||
class InterfacesWidget;
|
||||
class ExportsWidget;
|
||||
class SymbolsWidget;
|
||||
class RelocsWidget;
|
||||
class CommentsWidget;
|
||||
class StringsWidget;
|
||||
class FlagsWidget;
|
||||
class GlobalVariablesWidget;
|
||||
class Dashboard;
|
||||
class QLineEdit;
|
||||
class QRubberBand;
|
||||
class QTabBar;
|
||||
class SdbWidget;
|
||||
class QAction;
|
||||
class SectionsWidget;
|
||||
@@ -61,6 +67,11 @@ class FilesWidget;
|
||||
class FilesystemWidget;
|
||||
class BinariesWidget;
|
||||
class ZoomWidget;
|
||||
class DataAnalysisWidget;
|
||||
class XrefsWidget;
|
||||
class RefsWidget;
|
||||
class CodeInfoWidget;
|
||||
class DockManager;
|
||||
|
||||
namespace Ui {
|
||||
class MainWindow;
|
||||
@@ -102,6 +113,8 @@ public:
|
||||
|
||||
void closeEvent(QCloseEvent *event) override;
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
QMenu *createPopupMenu() override;
|
||||
void readSettings();
|
||||
void saveSettings();
|
||||
void setFilename(const QString &fn);
|
||||
@@ -122,7 +135,9 @@ public:
|
||||
addPluginDockWidget(dockWidget);
|
||||
}
|
||||
void addPluginDockWidget(IaitoDockWidget *dockWidget);
|
||||
enum class MenuType { File, Edit, View, Windows, Debug, Help, Plugins };
|
||||
QStringList getPanelNames() const;
|
||||
bool focusPanelByName(const QString &name);
|
||||
enum class MenuType { File, Edit, Code, View, Tools, Windows, Debug, Help, Plugins };
|
||||
/**
|
||||
* @brief Getter for MainWindow's different menus
|
||||
* @param type The type which represents the desired menu
|
||||
@@ -132,16 +147,20 @@ public:
|
||||
void addMenuFileAction(QAction *action);
|
||||
|
||||
QString getFilename() const { return filename; }
|
||||
QAction *getOverviewAction() const { return actionOverview; }
|
||||
void messageBoxWarning(QString title, QString message);
|
||||
|
||||
QString getUniqueObjectName(const QString &widgetType) const;
|
||||
void showMemoryWidget();
|
||||
void showMemoryWidget(MemoryWidgetType type);
|
||||
void showDataAnalysis(RVA start, RVA end);
|
||||
void gotoOffset(const QString &offset);
|
||||
enum class AddressTypeHint { Function, Data, Unknown };
|
||||
QMenu *createShowInMenu(
|
||||
QWidget *parent, RVA address, AddressTypeHint addressType = AddressTypeHint::Unknown);
|
||||
void setCurrentMemoryWidget(MemoryDockWidget *memoryWidget);
|
||||
MemoryDockWidget *getLastMemoryWidget();
|
||||
void raiseDefaultMemoryWidget();
|
||||
|
||||
/* Context menu plugins */
|
||||
enum class ContextMenuType { Disassembly, Addressable };
|
||||
@@ -174,10 +193,18 @@ public slots:
|
||||
void on_actionTabs_triggered();
|
||||
|
||||
void on_actionAnalyze_triggered();
|
||||
void on_actionAnalyzeFunction_triggered();
|
||||
void on_actionAutonameAll_triggered();
|
||||
void on_actionSwitchJumpTable_triggered();
|
||||
void on_actionAnalysisSettings_triggered();
|
||||
void on_actionPatchInstruction_triggered();
|
||||
void on_actionPatchString_triggered();
|
||||
void on_actionPatchBytes_triggered();
|
||||
|
||||
void lockDocks(bool lock);
|
||||
|
||||
void on_actionRun_Script_triggered();
|
||||
void on_actionRunR2jsScript_triggered();
|
||||
void on_actionScripts_triggered();
|
||||
|
||||
void toggleResponsive(bool maybe);
|
||||
@@ -207,6 +234,7 @@ private slots:
|
||||
void on_actionDefault_triggered();
|
||||
|
||||
void on_actionNew_triggered();
|
||||
void on_actionManageAddressSpace_triggered();
|
||||
|
||||
void on_actionSave_triggered();
|
||||
void on_actionSaveAs_triggered();
|
||||
@@ -225,14 +253,20 @@ private slots:
|
||||
void on_actionHighlight_triggered();
|
||||
void on_actionRefresh_contents_triggered();
|
||||
|
||||
void on_actionPreferences_triggered();
|
||||
void on_actionSettings_triggered();
|
||||
void on_actionStart_Web_Server_triggered(bool checked);
|
||||
|
||||
void on_actionImportPDB_triggered();
|
||||
void on_actionImportSymbols_triggered();
|
||||
void on_actionImportDWARF_triggered();
|
||||
void on_actionImportFLIRT_triggered();
|
||||
void on_actionImportProjectArchive_triggered();
|
||||
|
||||
void on_actionExport_as_code_triggered();
|
||||
void on_actionDump_triggered();
|
||||
void on_actionExportClassdump_triggered();
|
||||
void on_actionExportDWARF_triggered();
|
||||
void on_actionExportProjectArchive_triggered();
|
||||
|
||||
void on_actionGrouped_dock_dragging_triggered(bool checked);
|
||||
|
||||
@@ -255,8 +289,23 @@ private slots:
|
||||
void repositionSuperBottomNavbar();
|
||||
|
||||
private:
|
||||
enum class PendingCloseAction { None, Save, Discard };
|
||||
|
||||
struct WriteRedoEntry
|
||||
{
|
||||
RVA address;
|
||||
QByteArray data;
|
||||
};
|
||||
|
||||
IaitoCore *core;
|
||||
|
||||
void runCommandShortcut(const QString &command, bool needsInput);
|
||||
void undoWriteCache();
|
||||
void redoWriteCache();
|
||||
QByteArray currentWriteCacheFingerprint(bool *hasUndo = nullptr);
|
||||
bool completePendingClose();
|
||||
void resumePendingClose();
|
||||
|
||||
bool tabsOnTop;
|
||||
ut64 hexdumpTopOffset;
|
||||
ut64 hexdumpBottomOffset;
|
||||
@@ -269,17 +318,22 @@ private:
|
||||
QToolButton *webserverButton = nullptr;
|
||||
bool webserverRunning = false;
|
||||
QByteArray emptyState;
|
||||
QList<WriteRedoEntry> writeRedoStack;
|
||||
QByteArray writeRedoCacheFingerprint;
|
||||
IOModesController ioModesController;
|
||||
PendingCloseAction pendingCloseAction = PendingCloseAction::None;
|
||||
bool pendingProjectSave = false;
|
||||
bool pendingCloseScheduled = false;
|
||||
|
||||
Configuration *configuration;
|
||||
|
||||
QList<IaitoDockWidget *> dockWidgets;
|
||||
QList<IaitoDockWidget *> pluginDocks;
|
||||
DockManager *m_dockManager = nullptr;
|
||||
OverviewWidget *overviewDock = nullptr;
|
||||
QAction *actionOverview = nullptr;
|
||||
EntrypointWidget *entrypointDock = nullptr;
|
||||
FunctionsWidget *functionsDock = nullptr;
|
||||
ImportsWidget *importsDock = nullptr;
|
||||
InterfacesWidget *interfacesDock = nullptr;
|
||||
ExportsWidget *exportsDock = nullptr;
|
||||
HeadersWidget *headersDock = nullptr;
|
||||
TypesWidget *typesDock = nullptr;
|
||||
@@ -289,6 +343,7 @@ private:
|
||||
CommentsWidget *commentsDock = nullptr;
|
||||
StringsWidget *stringsDock = nullptr;
|
||||
FlagsWidget *flagsDock = nullptr;
|
||||
GlobalVariablesWidget *globalVariablesDock = nullptr;
|
||||
Dashboard *dashboardDock = nullptr;
|
||||
SdbWidget *sdbDock = nullptr;
|
||||
SectionsWidget *sectionsDock = nullptr;
|
||||
@@ -315,6 +370,10 @@ private:
|
||||
CallGraphWidget *callGraphDock = nullptr;
|
||||
CallGraphWidget *globalCallGraphDock = nullptr;
|
||||
ZoomWidget *zoomDock = nullptr;
|
||||
DataAnalysisWidget *dataAnalysisDock = nullptr;
|
||||
XrefsWidget *xrefsDock = nullptr;
|
||||
RefsWidget *refsDock = nullptr;
|
||||
CodeInfoWidget *codeInfoDock = nullptr;
|
||||
|
||||
QMenu *disassemblyContextMenuExtensions = nullptr;
|
||||
QMenu *addressableContextMenuExtensions = nullptr;
|
||||
@@ -324,6 +383,8 @@ private:
|
||||
void initUI();
|
||||
void initToolBar();
|
||||
void initDocks();
|
||||
void applyTopLevelMenuIcons();
|
||||
void fillTopLevelMenuIconGaps();
|
||||
void initBackForwardMenu();
|
||||
void displayInitialOptionsDialog(
|
||||
const InitialOptions &options = InitialOptions(),
|
||||
@@ -346,6 +407,12 @@ private:
|
||||
* @param widget that needs to be docked
|
||||
*/
|
||||
void dockOnMainArea(QDockWidget *widget);
|
||||
/**
|
||||
* @brief Layout invariant guard: guarantees at least one panel is always
|
||||
* visible and docked, recovering a main view if the user closed/tore off
|
||||
* everything. Prevents a blank main window or empty centre.
|
||||
*/
|
||||
void ensureMainAreaNotEmpty();
|
||||
void enableDebugWidgetsMenu(bool enable);
|
||||
/**
|
||||
* @brief Fill menu with seek history entries.
|
||||
@@ -353,9 +420,16 @@ private:
|
||||
* @param redo set to false for undo history, true for redo.
|
||||
*/
|
||||
void updateHistoryMenu(QMenu *menu, bool redo = false);
|
||||
IaitoSeekable *navigationSeekable(QWidget *origin = nullptr) const;
|
||||
void updateSaveProjectAction();
|
||||
void updateWriteUndoRedoActions();
|
||||
void rebuildRecentScriptsMenu();
|
||||
void updateLayoutsMenu();
|
||||
void saveNamedLayout();
|
||||
void manageLayouts();
|
||||
void runScriptFromDialog(const QString &caption, const QString &filter);
|
||||
void runScriptFile(const QString &fileName);
|
||||
void addRecentScript(const QString &fileName);
|
||||
|
||||
void setOverviewData();
|
||||
bool isOverviewActive();
|
||||
@@ -370,17 +444,14 @@ private:
|
||||
*/
|
||||
bool isDebugWidget(QDockWidget *dock) const;
|
||||
bool isExtraMemoryWidget(QDockWidget *dock) const;
|
||||
void applyDefaultSideDockWidths(QDockWidget *mainDock);
|
||||
void clearDefaultSideDockWidths();
|
||||
|
||||
MemoryWidgetType getMemoryWidgetTypeToRestore();
|
||||
|
||||
/**
|
||||
* @brief Map from a widget type (e.g. DisassemblyWidget::getWidgetType())
|
||||
* to the respective contructor of the widget
|
||||
*/
|
||||
QMap<QString, std::function<IaitoDockWidget *(MainWindow *)>> widgetTypeToConstructorMap;
|
||||
|
||||
MemoryDockWidget *lastSyncMemoryWidget = nullptr;
|
||||
MemoryDockWidget *lastMemoryWidget = nullptr;
|
||||
QPointer<AddressableDockWidget> navigationDock;
|
||||
int functionDockWidthToRestore = 0;
|
||||
|
||||
// True when the main window UI has been fully initialized and it's safe to
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
</property>
|
||||
<widget class="QMenu" name="addExtraWidgets">
|
||||
<property name="title">
|
||||
<string>Add extra...</string>
|
||||
<string>New Panel</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QMenuBar" name="menuBar">
|
||||
@@ -57,27 +57,57 @@
|
||||
<height>311</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QMenu" name="menuImport">
|
||||
<property name="title">
|
||||
<string>Import</string>
|
||||
</property>
|
||||
<addaction name="actionMap"/>
|
||||
<addaction name="actionImportPDB"/>
|
||||
<addaction name="actionImportSymbols"/>
|
||||
<addaction name="actionImportDWARF"/>
|
||||
<addaction name="actionImportFLIRT"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionImportProjectArchive"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuExport">
|
||||
<property name="title">
|
||||
<string>Export</string>
|
||||
</property>
|
||||
<addaction name="actionExport_as_code"/>
|
||||
<addaction name="actionDump"/>
|
||||
<addaction name="actionExportClassdump"/>
|
||||
<addaction name="actionExportDWARF"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionExportProjectArchive"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuRun">
|
||||
<property name="title">
|
||||
<string>Run</string>
|
||||
</property>
|
||||
<addaction name="actionRun_Script"/>
|
||||
<addaction name="actionRunR2jsScript"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionScripts"/>
|
||||
</widget>
|
||||
<property name="title">
|
||||
<string>File</string>
|
||||
</property>
|
||||
<string>File</string>
|
||||
</property>
|
||||
<addaction name="actionNew"/>
|
||||
<addaction name="actionMap"/>
|
||||
<addaction name="actionManageAddressSpace"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionImportPDB"/>
|
||||
<addaction name="actionImportSymbols"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionCommitChanges"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionSave"/>
|
||||
<addaction name="actionSaveAs"/>
|
||||
<addaction name="actionExport_as_code"/>
|
||||
<addaction name="actionDump"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionRun_Script"/>
|
||||
<addaction name="actionScripts"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionQuit"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionSave"/>
|
||||
<addaction name="actionSaveAs"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="menuImport"/>
|
||||
<addaction name="menuExport"/>
|
||||
<addaction name="menuRun"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionCommitChanges"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionSettings"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionQuit"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuView">
|
||||
<property name="title">
|
||||
@@ -95,26 +125,25 @@
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionZoomReset"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuLayouts">
|
||||
<widget class="QMenu" name="menuAnalysis">
|
||||
<property name="title">
|
||||
<string>Layouts</string>
|
||||
<string>Analysis</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuData">
|
||||
<property name="title">
|
||||
<string>Data</string>
|
||||
</property>
|
||||
</widget>
|
||||
<addaction name="actionRefresh_contents"/>
|
||||
<addaction name="actionHighlight"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionDefault"/>
|
||||
<addaction name="actionReset_settings"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionUnlock"/>
|
||||
<addaction name="actionTabs_on_Top"/>
|
||||
<addaction name="actionGrouped_dock_dragging"/>
|
||||
<addaction name="menuAnalysis"/>
|
||||
<addaction name="menuData"/>
|
||||
<addaction name="menuAddInfoWidgets"/>
|
||||
<addaction name="menuAddIoWidgets"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="menuAddAnother"/>
|
||||
<addaction name="menuZoom"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionManageLayouts"/>
|
||||
<addaction name="actionSaveLayout"/>
|
||||
<addaction name="menuLayouts"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuHelp">
|
||||
<property name="title">
|
||||
@@ -135,14 +164,14 @@
|
||||
<addaction name="actionBackward"/>
|
||||
<addaction name="actionForward"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionAnalyze"/>
|
||||
<addaction name="actionUndoWrite"/>
|
||||
<addaction name="actionRedoWrite"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionPatchInstruction"/>
|
||||
<addaction name="actionPatchString"/>
|
||||
<addaction name="actionPatchBytes"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="menuSetMode"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionPreferences"/>
|
||||
<addaction name="actionPackageManagerPrefs"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionStart_Web_Server"/>
|
||||
<widget class="QMenu" name="menuSetMode">
|
||||
<property name="toolTip">
|
||||
<string/>
|
||||
@@ -155,24 +184,60 @@
|
||||
<addaction name="actionReadOnly"/>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuWindows">
|
||||
<widget class="QMenu" name="menuCode">
|
||||
<property name="title">
|
||||
<string>Windows</string>
|
||||
<string>Code</string>
|
||||
</property>
|
||||
<widget class="QMenu" name="menuPlugins">
|
||||
<widget class="QMenu" name="menuPlugins">
|
||||
<property name="title">
|
||||
<string>Plugins</string>
|
||||
</property>
|
||||
<addaction name="actionR2pm"/>
|
||||
</widget>
|
||||
<addaction name="actionAnalyze"/>
|
||||
<addaction name="actionAnalyzeFunction"/>
|
||||
<addaction name="actionAutonameAll"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionSwitchJumpTable"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionAnalysisSettings"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="menuPlugins"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuTools">
|
||||
<property name="title">
|
||||
<string>Tools</string>
|
||||
</property>
|
||||
<addaction name="actionStart_Web_Server"/>
|
||||
<addaction name="actionPackageManagerPrefs"/>
|
||||
<addaction name="actionScripts"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuWindows">
|
||||
<property name="title">
|
||||
<string>Window</string>
|
||||
</property>
|
||||
<widget class="QMenu" name="menuWindowLayout">
|
||||
<property name="title">
|
||||
<string>Layout</string>
|
||||
</property>
|
||||
<widget class="QMenu" name="menuLayouts">
|
||||
<property name="title">
|
||||
<string>Layouts</string>
|
||||
</property>
|
||||
</widget>
|
||||
<addaction name="actionDefault"/>
|
||||
<addaction name="actionSaveLayout"/>
|
||||
<addaction name="actionManageLayouts"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="menuLayouts"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuAddInfoWidgets">
|
||||
<property name="title">
|
||||
<string>Info...</string>
|
||||
<string>Bin headers</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuAddIoWidgets">
|
||||
<property name="title">
|
||||
<string>IO...</string>
|
||||
<string>I/O Panels</string>
|
||||
</property>
|
||||
<!--
|
||||
<addaction name="actionBinaries"/>
|
||||
@@ -183,20 +248,28 @@
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuAddDebugWidgets">
|
||||
<property name="title">
|
||||
<string>Debug...</string>
|
||||
<string>Debug Panels</string>
|
||||
</property>
|
||||
</widget>
|
||||
<addaction name="actionExtraDecompiler"/>
|
||||
<addaction name="actionExtraDisassembly"/>
|
||||
<addaction name="actionExtraGraph"/>
|
||||
<addaction name="actionExtraHexdump"/>
|
||||
<widget class="QMenu" name="menuAddAnother">
|
||||
<property name="title">
|
||||
<string>New Panel</string>
|
||||
</property>
|
||||
<addaction name="actionExtraDecompiler"/>
|
||||
<addaction name="actionExtraDisassembly"/>
|
||||
<addaction name="actionExtraGraph"/>
|
||||
<addaction name="actionExtraHexdump"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionAddCustomCommand"/>
|
||||
</widget>
|
||||
<addaction name="actionRefresh_contents"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="menuAddInfoWidgets"/>
|
||||
<addaction name="menuAddIoWidgets"/>
|
||||
<addaction name="menuAddDebugWidgets"/>
|
||||
<addaction name="menuPlugins"/>
|
||||
<addaction name="menuWindowLayout"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionAddCustomCommand"/>
|
||||
<addaction name="actionTabs_on_Top"/>
|
||||
<addaction name="actionGrouped_dock_dragging"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionReset_settings"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuDebug">
|
||||
<property name="title">
|
||||
@@ -206,8 +279,10 @@
|
||||
<addaction name="menuFile"/>
|
||||
<addaction name="menuEdit"/>
|
||||
<addaction name="menuView"/>
|
||||
<addaction name="menuWindows"/>
|
||||
<addaction name="menuCode"/>
|
||||
<addaction name="menuDebug"/>
|
||||
<addaction name="menuTools"/>
|
||||
<addaction name="menuWindows"/>
|
||||
<addaction name="menuHelp"/>
|
||||
</widget>
|
||||
<widget class="QToolBar" name="mainToolBar">
|
||||
@@ -274,10 +349,7 @@
|
||||
</action>
|
||||
<action name="actionNew">
|
||||
<property name="text">
|
||||
<string>New Window</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+N</string>
|
||||
<string>Open…</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionClose">
|
||||
@@ -315,18 +387,23 @@
|
||||
</action>
|
||||
<action name="actionMap">
|
||||
<property name="text">
|
||||
<string>Map in memory</string>
|
||||
<string>Map file at…</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+M</string>
|
||||
</action>
|
||||
<action name="actionManageAddressSpace">
|
||||
<property name="text">
|
||||
<string>Manage files</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Manage files, maps, binaries, filesystems, and address space options</string>
|
||||
</property>
|
||||
<property name="statusTip">
|
||||
<string>Open the address space manager</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionSave">
|
||||
<property name="text">
|
||||
<string>Save</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+S</string>
|
||||
<string>Save Project</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionCut">
|
||||
@@ -399,31 +476,6 @@
|
||||
<string>Redo Seek</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionUnlock">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../resources.qrc">
|
||||
<normaloff>:/lock</normaloff>
|
||||
<normalon>:/unlock</normalon>:/lock</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Unlock Panels</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Toggle panel locks</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Meta+L</string>
|
||||
</property>
|
||||
<property name="iconVisibleInMenu">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionTheme">
|
||||
<property name="checkable">
|
||||
<bool>false</bool>
|
||||
@@ -693,41 +745,49 @@
|
||||
</action>
|
||||
<action name="actionRun_Script">
|
||||
<property name="text">
|
||||
<string>Run radare2 script</string>
|
||||
<string>Radare Script…</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Run a script file once</string>
|
||||
<string>Run a radare2 script file once</string>
|
||||
</property>
|
||||
<property name="statusTip">
|
||||
<string>Quickly execute a script file in the current session</string>
|
||||
<string>Select and execute a radare2 script file in the current session</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionRunR2jsScript">
|
||||
<property name="text">
|
||||
<string>r2js script…</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Run an r2js script file once</string>
|
||||
</property>
|
||||
<property name="statusTip">
|
||||
<string>Select and execute an r2js script file in the current session</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionScripts">
|
||||
<property name="text">
|
||||
<string>Scripts...</string>
|
||||
<string>Script Editor</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Open the scripts manager</string>
|
||||
<string>Open the script editor</string>
|
||||
</property>
|
||||
<property name="statusTip">
|
||||
<string>Edit startup and on-load scripts, manage saved scripts, and run them with inline output</string>
|
||||
<string>Edit, save, and run scripts with inline output</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionReset_settings">
|
||||
<property name="text">
|
||||
<string>Reset Settings</string>
|
||||
<string>Factory reset</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Reset settings</string>
|
||||
<string>Reset application settings</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionQuit">
|
||||
<property name="text">
|
||||
<string>Quit</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+Q</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionExports">
|
||||
<property name="checkable">
|
||||
@@ -755,9 +815,6 @@
|
||||
<property name="toolTip">
|
||||
<string>Highlight matching words in disassembly</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Meta+F</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionDisplay_Esil">
|
||||
<property name="checkable">
|
||||
@@ -786,9 +843,9 @@
|
||||
<string>Display offsets</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionPreferences">
|
||||
<action name="actionSettings">
|
||||
<property name="text">
|
||||
<string>Preferences</string>
|
||||
<string>Settings</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionPackageManagerPrefs">
|
||||
@@ -798,7 +855,7 @@
|
||||
</action>
|
||||
<action name="actionSaveAs">
|
||||
<property name="text">
|
||||
<string>Save As...</string>
|
||||
<string>Save Project…</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionGraph">
|
||||
@@ -811,12 +868,12 @@
|
||||
</action>
|
||||
<action name="actionImportPDB">
|
||||
<property name="text">
|
||||
<string>Import PDB</string>
|
||||
<string>PDB file…</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionImportSymbols">
|
||||
<property name="text">
|
||||
<string>Import symbols</string>
|
||||
<string>Binary symbols…</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Import symbols from another binary object file</string>
|
||||
@@ -830,42 +887,125 @@
|
||||
<string>Analyze Program</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionAnalyzeFunction">
|
||||
<property name="text">
|
||||
<string>Analyze Function</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Analyze the function at the current offset</string>
|
||||
</property>
|
||||
<property name="statusTip">
|
||||
<string>Run af at the current seek position</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionAutonameAll">
|
||||
<property name="text">
|
||||
<string>Autoname All</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Autoname all functions</string>
|
||||
</property>
|
||||
<property name="statusTip">
|
||||
<string>Run aan to autoname all functions</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionAnalysisSettings">
|
||||
<property name="text">
|
||||
<string>Analysis Settings</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Open analysis settings</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionSwitchJumpTable">
|
||||
<property name="text">
|
||||
<string>SwitchJumpTable…</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Create or edit a switch / jump table at the current dispatcher</string>
|
||||
</property>
|
||||
<property name="statusTip">
|
||||
<string>Open the switch/jump table panel (afbt / afb+ / afbe)</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionExport_as_code">
|
||||
<property name="text">
|
||||
<string>Export...</string>
|
||||
<string>Disassembly/code…</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionDump">
|
||||
<property name="text">
|
||||
<string>Dump...</string>
|
||||
<string>Binary range…</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Dump a range of bytes to a file</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionExportClassdump">
|
||||
<property name="text">
|
||||
<string>Class dump…</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Export class information using icc</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionImportDWARF">
|
||||
<property name="text">
|
||||
<string>DWARF information…</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Import DWARF information from another binary object file</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionImportFLIRT">
|
||||
<property name="text">
|
||||
<string>FLIRT signatures…</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Load and scan FLIRT signature files</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionImportProjectArchive">
|
||||
<property name="text">
|
||||
<string>Project archive…</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionExportDWARF">
|
||||
<property name="text">
|
||||
<string>DWARF information…</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Export DWARF source line information</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionExportProjectArchive">
|
||||
<property name="text">
|
||||
<string>Project archive…</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionExtraHexdump">
|
||||
<property name="text">
|
||||
<string>Add Hexdump</string>
|
||||
<string>Hexdump</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionExtraDecompiler">
|
||||
<property name="text">
|
||||
<string>Add Decompiler</string>
|
||||
<string>Decompiler</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionExtraDisassembly">
|
||||
<property name="text">
|
||||
<string>Add Disassembly</string>
|
||||
<string>Disassembly</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionExtraGraph">
|
||||
<property name="text">
|
||||
<string>Add Graph</string>
|
||||
<string>Graph</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionAddCustomCommand">
|
||||
<property name="text">
|
||||
<string>Add Custom Command</string>
|
||||
<string>Custom Command</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionGrouped_dock_dragging">
|
||||
@@ -883,9 +1023,6 @@
|
||||
<property name="text">
|
||||
<string>Zoom In</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl++</string>
|
||||
</property>
|
||||
<property name="shortcutContext">
|
||||
<enum>Qt::ApplicationShortcut</enum>
|
||||
</property>
|
||||
@@ -894,9 +1031,6 @@
|
||||
<property name="text">
|
||||
<string>Zoom Out</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+-</string>
|
||||
</property>
|
||||
<property name="shortcutContext">
|
||||
<enum>Qt::ApplicationShortcut</enum>
|
||||
</property>
|
||||
@@ -905,16 +1039,53 @@
|
||||
<property name="text">
|
||||
<string>Reset</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+0</string>
|
||||
</property>
|
||||
<property name="shortcutContext">
|
||||
<enum>Qt::ApplicationShortcut</enum>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionCommitChanges">
|
||||
<property name="text">
|
||||
<string>Commit changes</string>
|
||||
<string>Commit iocache to disk</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionUndoWrite">
|
||||
<property name="text">
|
||||
<string>Undo write</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Undo the last pending write-cache change</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionRedoWrite">
|
||||
<property name="text">
|
||||
<string>Redo write</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Redo the last undone write-cache change</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionPatchInstruction">
|
||||
<property name="text">
|
||||
<string>Patch Instruction…</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Patch the instruction at the current address</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionPatchString">
|
||||
<property name="text">
|
||||
<string>Patch String…</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Patch a string at the current address</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionPatchBytes">
|
||||
<property name="text">
|
||||
<string>Patch Bytes…</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Patch bytes at the current address</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionWriteMode">
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
#include "QtDockBackend.h"
|
||||
|
||||
#include <QDockWidget>
|
||||
#include <QMainWindow>
|
||||
#include <QTabBar>
|
||||
#include <QTabWidget>
|
||||
|
||||
QtDockBackend::QtDockBackend(QMainWindow *host)
|
||||
: host(host)
|
||||
{}
|
||||
|
||||
void QtDockBackend::addDock(QDockWidget *dock, Qt::DockWidgetArea area)
|
||||
{
|
||||
host->addDockWidget(area, dock);
|
||||
}
|
||||
|
||||
void QtDockBackend::splitDock(QDockWidget *first, QDockWidget *second, Qt::Orientation orientation)
|
||||
{
|
||||
host->splitDockWidget(first, second, orientation);
|
||||
}
|
||||
|
||||
void QtDockBackend::tabifyDock(QDockWidget *first, QDockWidget *second)
|
||||
{
|
||||
host->tabifyDockWidget(first, second);
|
||||
}
|
||||
|
||||
void QtDockBackend::removeDock(QDockWidget *dock)
|
||||
{
|
||||
host->removeDockWidget(dock);
|
||||
}
|
||||
|
||||
void QtDockBackend::setDockFloating(QDockWidget *dock, bool floating)
|
||||
{
|
||||
dock->setFloating(floating);
|
||||
}
|
||||
|
||||
QList<QDockWidget *> QtDockBackend::tabifiedWith(QDockWidget *dock) const
|
||||
{
|
||||
return host->tabifiedDockWidgets(dock);
|
||||
}
|
||||
|
||||
Qt::DockWidgetArea QtDockBackend::areaOf(QDockWidget *dock) const
|
||||
{
|
||||
return host->dockWidgetArea(dock);
|
||||
}
|
||||
|
||||
void QtDockBackend::resizeDocksTo(
|
||||
const QList<QDockWidget *> &docks, const QList<int> &sizes, Qt::Orientation orientation)
|
||||
{
|
||||
host->resizeDocks(docks, sizes, orientation);
|
||||
}
|
||||
|
||||
QList<QTabBar *> QtDockBackend::dockTabBars() const
|
||||
{
|
||||
return host->findChildren<QTabBar *>();
|
||||
}
|
||||
|
||||
void QtDockBackend::setTabsOnTop(bool onTop)
|
||||
{
|
||||
host->setTabPosition(Qt::AllDockWidgetAreas, onTop ? QTabWidget::North : QTabWidget::South);
|
||||
}
|
||||
|
||||
void QtDockBackend::setGroupedDragging(bool enabled)
|
||||
{
|
||||
QMainWindow::DockOptions options = host->dockOptions();
|
||||
options.setFlag(QMainWindow::GroupedDragging, enabled);
|
||||
host->setDockOptions(options);
|
||||
}
|
||||
|
||||
QByteArray QtDockBackend::saveState() const
|
||||
{
|
||||
return host->saveState();
|
||||
}
|
||||
|
||||
bool QtDockBackend::restoreState(const QByteArray &state)
|
||||
{
|
||||
return host->restoreState(state);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
#ifndef QTDOCKBACKEND_H
|
||||
#define QTDOCKBACKEND_H
|
||||
|
||||
#include "DockBackend.h"
|
||||
|
||||
class QMainWindow;
|
||||
|
||||
// DockBackend realized on a QMainWindow's native QDockWidget docking. Every
|
||||
// method forwards to the equivalent QMainWindow/QDockWidget call, so behavior
|
||||
// stays identical to the inline implementation it replaces.
|
||||
class QtDockBackend : public DockBackend
|
||||
{
|
||||
public:
|
||||
explicit QtDockBackend(QMainWindow *host);
|
||||
|
||||
void addDock(QDockWidget *dock, Qt::DockWidgetArea area) override;
|
||||
void splitDock(QDockWidget *first, QDockWidget *second, Qt::Orientation orientation) override;
|
||||
void tabifyDock(QDockWidget *first, QDockWidget *second) override;
|
||||
void removeDock(QDockWidget *dock) override;
|
||||
void setDockFloating(QDockWidget *dock, bool floating) override;
|
||||
|
||||
QList<QDockWidget *> tabifiedWith(QDockWidget *dock) const override;
|
||||
Qt::DockWidgetArea areaOf(QDockWidget *dock) const override;
|
||||
void resizeDocksTo(
|
||||
const QList<QDockWidget *> &docks,
|
||||
const QList<int> &sizes,
|
||||
Qt::Orientation orientation) override;
|
||||
|
||||
QList<QTabBar *> dockTabBars() const override;
|
||||
void setTabsOnTop(bool onTop) override;
|
||||
void setGroupedDragging(bool enabled) override;
|
||||
|
||||
QByteArray saveState() const override;
|
||||
bool restoreState(const QByteArray &state) override;
|
||||
|
||||
private:
|
||||
QMainWindow *host;
|
||||
};
|
||||
|
||||
#endif // QTDOCKBACKEND_H
|
||||