mirror of https://github.com/encounter/objdiff.git
Version 0.2.0
- Update checker & auto-updater - Configure font sizes and diff colors - Data diffing bug fixes & improvements - Bug fix for low match percent - Improvements to Jobs UI (cancel, dismiss errors) - "Demangle" tool Closes #6, #13, #17, #19
This commit is contained in:
parent
2f2efb4711
commit
771a141110
|
@ -8,67 +8,126 @@ on:
|
||||||
- 'LICENSE*'
|
- 'LICENSE*'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
|
env:
|
||||||
|
CARGO_BIN_NAME: objdiff
|
||||||
|
CARGO_TARGET_DIR: target
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check:
|
check:
|
||||||
name: Check
|
name: Check
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
toolchain: [ stable, 1.62.0, nightly ]
|
|
||||||
fail-fast: false
|
|
||||||
env:
|
env:
|
||||||
RUSTFLAGS: -D warnings
|
RUSTFLAGS: -D warnings
|
||||||
steps:
|
steps:
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: sudo apt-get -y install libgtk-3-dev
|
||||||
sudo apt install libgtk-3-dev
|
- name: Checkout
|
||||||
- uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
- uses: actions-rs/toolchain@v1
|
- name: Setup Rust toolchain
|
||||||
|
uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
profile: minimal
|
|
||||||
toolchain: ${{ matrix.toolchain }}
|
|
||||||
override: true
|
|
||||||
components: rustfmt, clippy
|
components: rustfmt, clippy
|
||||||
- uses: EmbarkStudios/cargo-deny-action@v1
|
- name: Cargo check
|
||||||
- uses: actions-rs/cargo@v1
|
run: cargo check --all-features
|
||||||
with:
|
- name: Cargo clippy
|
||||||
command: check
|
run: cargo clippy --all-features
|
||||||
args: --all-features
|
|
||||||
- uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
command: clippy
|
|
||||||
args: --all-features
|
|
||||||
|
|
||||||
build:
|
deny:
|
||||||
name: Build
|
name: Deny
|
||||||
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
platform: [ ubuntu-latest, macos-latest, windows-latest ]
|
checks:
|
||||||
toolchain: [ stable, 1.62.0, nightly ]
|
- advisories
|
||||||
|
- bans licenses sources
|
||||||
|
# Prevent new advisories from failing CI
|
||||||
|
continue-on-error: ${{ matrix.checks == 'advisories' }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: EmbarkStudios/cargo-deny-action@v1
|
||||||
|
with:
|
||||||
|
command: check ${{ matrix.checks }}
|
||||||
|
|
||||||
|
test:
|
||||||
|
name: Test
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
platform: [ ubuntu-latest, windows-latest, macos-latest ]
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
steps:
|
steps:
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
if: matrix.platform == 'ubuntu-latest'
|
if: matrix.platform == 'ubuntu-latest'
|
||||||
run: |
|
run: sudo apt-get -y install libgtk-3-dev
|
||||||
sudo apt install libgtk-3-dev
|
- name: Checkout
|
||||||
- uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
- uses: actions-rs/toolchain@v1
|
- name: Setup Rust toolchain
|
||||||
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
- name: Cargo test
|
||||||
|
run: cargo test --release --all-features
|
||||||
|
|
||||||
|
build:
|
||||||
|
name: Build
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- platform: ubuntu-latest
|
||||||
|
target: x86_64-unknown-linux-gnu
|
||||||
|
name: linux-x86_64
|
||||||
|
packages: libgtk-3-dev
|
||||||
|
- platform: windows-latest
|
||||||
|
target: x86_64-pc-windows-msvc
|
||||||
|
name: windows-x86_64
|
||||||
|
- platform: macos-latest
|
||||||
|
target: x86_64-apple-darwin
|
||||||
|
name: macos-x86_64
|
||||||
|
- platform: macos-latest
|
||||||
|
target: aarch64-apple-darwin
|
||||||
|
name: macos-arm64
|
||||||
|
fail-fast: false
|
||||||
|
runs-on: ${{ matrix.platform }}
|
||||||
|
steps:
|
||||||
|
- name: Install dependencies
|
||||||
|
if: matrix.packages != ''
|
||||||
|
run: sudo apt-get -y install ${{ matrix.packages }}
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Setup Rust toolchain
|
||||||
|
uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
profile: minimal
|
targets: ${{ matrix.target }}
|
||||||
toolchain: ${{ matrix.toolchain }}
|
- name: Cargo build
|
||||||
override: true
|
run: cargo build --release --all-features --target ${{ matrix.target }} --bin ${{ env.CARGO_BIN_NAME }}
|
||||||
- uses: actions-rs/cargo@v1
|
- name: Upload artifacts
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
command: test
|
name: ${{ matrix.name }}
|
||||||
args: --release --all-features
|
|
||||||
- uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
command: build
|
|
||||||
args: --release --all-features
|
|
||||||
- uses: actions/upload-artifact@v2
|
|
||||||
with:
|
|
||||||
name: ${{ matrix.platform }}-${{ matrix.toolchain }}
|
|
||||||
path: |
|
path: |
|
||||||
target/release/objdiff
|
${{ env.CARGO_TARGET_DIR }}/release/${{ env.CARGO_BIN_NAME }}
|
||||||
target/release/objdiff.exe
|
${{ env.CARGO_TARGET_DIR }}/release/${{ env.CARGO_BIN_NAME }}.exe
|
||||||
|
${{ env.CARGO_TARGET_DIR }}/${{ matrix.target }}/release/${{ env.CARGO_BIN_NAME }}
|
||||||
|
${{ env.CARGO_TARGET_DIR }}/${{ matrix.target }}/release/${{ env.CARGO_BIN_NAME }}.exe
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
release:
|
||||||
|
name: Release
|
||||||
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [ build ]
|
||||||
|
steps:
|
||||||
|
- name: Download artifacts
|
||||||
|
uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
path: artifacts
|
||||||
|
- name: Rename artifacts
|
||||||
|
working-directory: artifacts
|
||||||
|
run: |
|
||||||
|
mkdir ../out
|
||||||
|
for i in */*/release/$CARGO_BIN_NAME*; do
|
||||||
|
mv "$i" "../out/$(sed -E "s/([^/]+)\/[^/]+\/release\/($CARGO_BIN_NAME)/\2-\1/" <<< "$i")"
|
||||||
|
done
|
||||||
|
ls -R ../out
|
||||||
|
- name: Release
|
||||||
|
uses: softprops/action-gh-release@v1
|
||||||
|
with:
|
||||||
|
files: out/*
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
50
Cargo.toml
50
Cargo.toml
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "objdiff"
|
name = "objdiff"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.62"
|
rust-version = "1.62"
|
||||||
authors = ["Luke Street <luke@street.dev>"]
|
authors = ["Luke Street <luke@street.dev>"]
|
||||||
|
@ -8,35 +8,55 @@ license = "MIT OR Apache-2.0"
|
||||||
repository = "https://github.com/encounter/objdiff"
|
repository = "https://github.com/encounter/objdiff"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
description = """
|
description = """
|
||||||
A tool for decompilation projects.
|
A local diffing tool for decompilation projects.
|
||||||
"""
|
"""
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
lto = "thin"
|
||||||
|
strip = "debuginfo"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
egui = "0.19.0"
|
anyhow = "1.0.66"
|
||||||
|
cfg-if = "1.0.0"
|
||||||
|
const_format = "0.2.30"
|
||||||
|
cwdemangle = { git = "https://github.com/encounter/cwdemangle", rev = "286f3d1d29ee2457db89043782725631845c3e4c" }
|
||||||
eframe = { version = "0.19.0", features = ["persistence"] } # , "wgpu"
|
eframe = { version = "0.19.0", features = ["persistence"] } # , "wgpu"
|
||||||
serde = { version = "1", features = ["derive"] }
|
egui = "0.19.0"
|
||||||
anyhow = "1.0.63"
|
|
||||||
thiserror = "1.0.33"
|
|
||||||
flagset = "0.4.3"
|
|
||||||
object = "0.29.0"
|
|
||||||
notify = "5.0.0"
|
|
||||||
cwdemangle = { git = "https://github.com/encounter/cwdemangle", rev = "64e8b3e083343783c5b3b6329ea940f375b057b3" }
|
|
||||||
log = "0.4.17"
|
|
||||||
rfd = { version = "0.10.0" } # , default-features = false, features = ['xdg-portal']
|
|
||||||
egui_extras = "0.19.0"
|
egui_extras = "0.19.0"
|
||||||
|
flagset = "0.4.3"
|
||||||
|
log = "0.4.17"
|
||||||
|
memmap2 = "0.5.8"
|
||||||
|
notify = "5.0.0"
|
||||||
|
object = { version = "0.30.0", features = ["read_core", "std", "elf"], default-features = false }
|
||||||
ppc750cl = { git = "https://github.com/encounter/ppc750cl", rev = "aa631a33de7882c679afca89350898b87cb3ba3f" }
|
ppc750cl = { git = "https://github.com/encounter/ppc750cl", rev = "aa631a33de7882c679afca89350898b87cb3ba3f" }
|
||||||
rabbitizer = { git = "https://github.com/encounter/rabbitizer-rs", rev = "10c279b2ef251c62885b1dcdcfe740b0db8e9956" }
|
rabbitizer = { git = "https://github.com/encounter/rabbitizer-rs", rev = "10c279b2ef251c62885b1dcdcfe740b0db8e9956" }
|
||||||
time = { version = "0.3.14", features = ["formatting", "local-offset"] }
|
rfd = { version = "0.10.0" } # , default-features = false, features = ['xdg-portal']
|
||||||
|
self_update = "0.32.0"
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
thiserror = "1.0.37"
|
||||||
|
time = { version = "0.3.17", features = ["formatting", "local-offset"] }
|
||||||
|
toml = "0.5.9"
|
||||||
|
twox-hash = "1.6.3"
|
||||||
|
tempfile = "3.3.0"
|
||||||
|
reqwest = "0.11.13"
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
path-slash = "0.2.0"
|
path-slash = "0.2.1"
|
||||||
winapi = "0.3.9"
|
winapi = "0.3.9"
|
||||||
|
|
||||||
|
[target.'cfg(unix)'.dependencies]
|
||||||
|
exec = "0.3.1"
|
||||||
|
|
||||||
# native:
|
# native:
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
tracing-subscriber = "0.3"
|
tracing-subscriber = "0.3"
|
||||||
|
|
||||||
# web:
|
# web:
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
console_error_panic_hook = "0.1.6"
|
console_error_panic_hook = "0.1.7"
|
||||||
tracing-wasm = "0.2"
|
tracing-wasm = "0.2"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
anyhow = "1.0.66"
|
||||||
|
vergen = { version = "7.4.3", features = ["build", "cargo", "git"], default-features = false }
|
||||||
|
|
|
@ -0,0 +1,201 @@
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright 2022 Luke Street.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright 2022 Luke Street.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -3,14 +3,12 @@
|
||||||
[Build Status]: https://github.com/encounter/objdiff/actions/workflows/build.yaml/badge.svg
|
[Build Status]: https://github.com/encounter/objdiff/actions/workflows/build.yaml/badge.svg
|
||||||
[actions]: https://github.com/encounter/objdiff/actions
|
[actions]: https://github.com/encounter/objdiff/actions
|
||||||
|
|
||||||
A tool for decompilation projects.
|
A local diffing tool for decompilation projects.
|
||||||
|
|
||||||
Currently supports:
|
Currently supports:
|
||||||
- PowerPC 750CL (GameCube & Wii)
|
- PowerPC 750CL (GameCube & Wii)
|
||||||
- MIPS (Nintendo 64)
|
- MIPS (Nintendo 64)
|
||||||
|
|
||||||
**WARNING:** Very early & unstable.
|
|
||||||
|
|
||||||
![Symbol Screenshot](assets/screen-symbols.png)
|
![Symbol Screenshot](assets/screen-symbols.png)
|
||||||
![Diff Screenshot](assets/screen-diff.png)
|
![Diff Screenshot](assets/screen-diff.png)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
use anyhow::Result;
|
||||||
|
use vergen::{vergen, Config};
|
||||||
|
|
||||||
|
fn main() -> Result<()> { vergen(Config::default()) }
|
228
src/app.rs
228
src/app.rs
|
@ -2,22 +2,23 @@ use std::{
|
||||||
default::Default,
|
default::Default,
|
||||||
ffi::OsStr,
|
ffi::OsStr,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
|
rc::Rc,
|
||||||
sync::{
|
sync::{
|
||||||
atomic::{AtomicBool, Ordering},
|
atomic::{AtomicBool, Ordering},
|
||||||
Arc, RwLock,
|
Arc, Mutex, RwLock,
|
||||||
},
|
},
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
use eframe::Frame;
|
use egui::{Color32, FontFamily, FontId, TextStyle};
|
||||||
use egui::Widget;
|
|
||||||
use notify::{RecursiveMode, Watcher};
|
use notify::{RecursiveMode, Watcher};
|
||||||
use time::{OffsetDateTime, UtcOffset};
|
use time::{OffsetDateTime, UtcOffset};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
jobs::{
|
jobs::{
|
||||||
|
check_update::{queue_check_update, CheckUpdateResult},
|
||||||
objdiff::{queue_build, BuildStatus, ObjDiffResult},
|
objdiff::{queue_build, BuildStatus, ObjDiffResult},
|
||||||
Job, JobResult, JobState,
|
Job, JobResult, JobState, JobStatus,
|
||||||
},
|
},
|
||||||
views::{
|
views::{
|
||||||
config::config_ui, data_diff::data_diff_ui, function_diff::function_diff_ui, jobs::jobs_ui,
|
config::config_ui, data_diff::data_diff_ui, function_diff::function_diff_ui, jobs::jobs_ui,
|
||||||
|
@ -48,6 +49,36 @@ pub struct DiffConfig {
|
||||||
// pub mapped_symbols: HashMap<String, String>,
|
// pub mapped_symbols: HashMap<String, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DEFAULT_COLOR_ROTATION: [Color32; 9] = [
|
||||||
|
Color32::from_rgb(255, 0, 255),
|
||||||
|
Color32::from_rgb(0, 255, 255),
|
||||||
|
Color32::from_rgb(0, 128, 0),
|
||||||
|
Color32::from_rgb(255, 0, 0),
|
||||||
|
Color32::from_rgb(255, 255, 0),
|
||||||
|
Color32::from_rgb(255, 192, 203),
|
||||||
|
Color32::from_rgb(0, 0, 255),
|
||||||
|
Color32::from_rgb(0, 255, 0),
|
||||||
|
Color32::from_rgb(128, 128, 128),
|
||||||
|
];
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, serde::Serialize)]
|
||||||
|
#[serde(default)]
|
||||||
|
pub struct ViewConfig {
|
||||||
|
pub ui_font: FontId,
|
||||||
|
pub code_font: FontId,
|
||||||
|
pub diff_colors: Vec<Color32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ViewConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
ui_font: FontId { size: 14.0, family: FontFamily::Proportional },
|
||||||
|
code_font: FontId { size: 14.0, family: FontFamily::Monospace },
|
||||||
|
diff_colors: DEFAULT_COLOR_ROTATION.to_vec(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize, serde::Serialize)]
|
#[derive(serde::Deserialize, serde::Serialize)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct ViewState {
|
pub struct ViewState {
|
||||||
|
@ -64,14 +95,21 @@ pub struct ViewState {
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub show_config: bool,
|
pub show_config: bool,
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
|
pub show_demangle: bool,
|
||||||
|
#[serde(skip)]
|
||||||
|
pub demangle_text: String,
|
||||||
|
#[serde(skip)]
|
||||||
pub diff_config: DiffConfig,
|
pub diff_config: DiffConfig,
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub search: String,
|
pub search: String,
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub utc_offset: UtcOffset,
|
pub utc_offset: UtcOffset,
|
||||||
|
#[serde(skip)]
|
||||||
|
pub check_update: Option<Box<CheckUpdateResult>>,
|
||||||
// Config
|
// Config
|
||||||
pub diff_kind: DiffKind,
|
pub diff_kind: DiffKind,
|
||||||
pub reverse_fn_order: bool,
|
pub reverse_fn_order: bool,
|
||||||
|
pub view_config: ViewConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ViewState {
|
impl Default for ViewState {
|
||||||
|
@ -83,11 +121,15 @@ impl Default for ViewState {
|
||||||
selected_symbol: None,
|
selected_symbol: None,
|
||||||
current_view: Default::default(),
|
current_view: Default::default(),
|
||||||
show_config: false,
|
show_config: false,
|
||||||
|
show_demangle: false,
|
||||||
|
demangle_text: String::new(),
|
||||||
diff_config: Default::default(),
|
diff_config: Default::default(),
|
||||||
search: Default::default(),
|
search: Default::default(),
|
||||||
utc_offset: UtcOffset::UTC,
|
utc_offset: UtcOffset::UTC,
|
||||||
|
check_update: None,
|
||||||
diff_kind: Default::default(),
|
diff_kind: Default::default(),
|
||||||
reverse_fn_order: false,
|
reverse_fn_order: false,
|
||||||
|
view_config: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -95,7 +137,7 @@ impl Default for ViewState {
|
||||||
#[derive(Default, Clone, serde::Deserialize, serde::Serialize)]
|
#[derive(Default, Clone, serde::Deserialize, serde::Serialize)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct AppConfig {
|
pub struct AppConfig {
|
||||||
pub custom_make: String,
|
pub custom_make: Option<String>,
|
||||||
// WSL2 settings
|
// WSL2 settings
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub available_wsl_distros: Option<Vec<String>>,
|
pub available_wsl_distros: Option<Vec<String>>,
|
||||||
|
@ -111,6 +153,19 @@ pub struct AppConfig {
|
||||||
pub right_obj: Option<PathBuf>,
|
pub right_obj: Option<PathBuf>,
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub project_dir_change: bool,
|
pub project_dir_change: bool,
|
||||||
|
#[serde(skip)]
|
||||||
|
pub queue_update_check: bool,
|
||||||
|
pub auto_update_check: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Clone, serde::Deserialize)]
|
||||||
|
#[serde(default)]
|
||||||
|
pub struct ProjectConfig {
|
||||||
|
pub custom_make: Option<String>,
|
||||||
|
pub project_dir: Option<PathBuf>,
|
||||||
|
pub target_obj_dir: Option<PathBuf>,
|
||||||
|
pub base_obj_dir: Option<PathBuf>,
|
||||||
|
pub build_target: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// We derive Deserialize/Serialize so we can persist app state on shutdown.
|
/// We derive Deserialize/Serialize so we can persist app state on shutdown.
|
||||||
|
@ -124,6 +179,10 @@ pub struct App {
|
||||||
modified: Arc<AtomicBool>,
|
modified: Arc<AtomicBool>,
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
watcher: Option<notify::RecommendedWatcher>,
|
watcher: Option<notify::RecommendedWatcher>,
|
||||||
|
#[serde(skip)]
|
||||||
|
relaunch_path: Rc<Mutex<Option<PathBuf>>>,
|
||||||
|
#[serde(skip)]
|
||||||
|
should_relaunch: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for App {
|
impl Default for App {
|
||||||
|
@ -133,6 +192,8 @@ impl Default for App {
|
||||||
config: Arc::new(Default::default()),
|
config: Arc::new(Default::default()),
|
||||||
modified: Arc::new(Default::default()),
|
modified: Arc::new(Default::default()),
|
||||||
watcher: None,
|
watcher: None,
|
||||||
|
relaunch_path: Default::default(),
|
||||||
|
should_relaunch: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -141,7 +202,11 @@ const CONFIG_KEY: &str = "app_config";
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
/// Called once before the first frame.
|
/// Called once before the first frame.
|
||||||
pub fn new(cc: &eframe::CreationContext<'_>, utc_offset: UtcOffset) -> Self {
|
pub fn new(
|
||||||
|
cc: &eframe::CreationContext<'_>,
|
||||||
|
utc_offset: UtcOffset,
|
||||||
|
relaunch_path: Rc<Mutex<Option<PathBuf>>>,
|
||||||
|
) -> Self {
|
||||||
// This is also where you can customized the look at feel of egui using
|
// This is also where you can customized the look at feel of egui using
|
||||||
// `cc.egui_ctx.set_visuals` and `cc.egui_ctx.set_fonts`.
|
// `cc.egui_ctx.set_visuals` and `cc.egui_ctx.set_fonts`.
|
||||||
|
|
||||||
|
@ -153,11 +218,16 @@ impl App {
|
||||||
if config.project_dir.is_some() {
|
if config.project_dir.is_some() {
|
||||||
config.project_dir_change = true;
|
config.project_dir_change = true;
|
||||||
}
|
}
|
||||||
|
config.queue_update_check = config.auto_update_check;
|
||||||
app.config = Arc::new(RwLock::new(config));
|
app.config = Arc::new(RwLock::new(config));
|
||||||
app.view_state.utc_offset = utc_offset;
|
app.view_state.utc_offset = utc_offset;
|
||||||
|
app.relaunch_path = relaunch_path;
|
||||||
app
|
app
|
||||||
} else {
|
} else {
|
||||||
Self::default()
|
let mut app = Self::default();
|
||||||
|
app.view_state.utc_offset = utc_offset;
|
||||||
|
app.relaunch_path = relaunch_path;
|
||||||
|
app
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -165,17 +235,44 @@ impl App {
|
||||||
impl eframe::App for App {
|
impl eframe::App for App {
|
||||||
/// Called each time the UI needs repainting, which may be many times per second.
|
/// Called each time the UI needs repainting, which may be many times per second.
|
||||||
/// Put your widgets into a `SidePanel`, `TopPanel`, `CentralPanel`, `Window` or `Area`.
|
/// Put your widgets into a `SidePanel`, `TopPanel`, `CentralPanel`, `Window` or `Area`.
|
||||||
fn update(&mut self, ctx: &egui::Context, frame: &mut Frame) {
|
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
||||||
|
if self.should_relaunch {
|
||||||
|
frame.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let Self { config, view_state, .. } = self;
|
let Self { config, view_state, .. } = self;
|
||||||
|
|
||||||
|
{
|
||||||
|
let config = &view_state.view_config;
|
||||||
|
let mut style = (*ctx.style()).clone();
|
||||||
|
style.text_styles.insert(TextStyle::Body, FontId {
|
||||||
|
size: (config.ui_font.size * 0.75).floor(),
|
||||||
|
family: config.ui_font.family.clone(),
|
||||||
|
});
|
||||||
|
style.text_styles.insert(TextStyle::Body, config.ui_font.clone());
|
||||||
|
style.text_styles.insert(TextStyle::Button, config.ui_font.clone());
|
||||||
|
style.text_styles.insert(TextStyle::Heading, FontId {
|
||||||
|
size: (config.ui_font.size * 1.5).floor(),
|
||||||
|
family: config.ui_font.family.clone(),
|
||||||
|
});
|
||||||
|
style.text_styles.insert(TextStyle::Monospace, config.code_font.clone());
|
||||||
|
ctx.set_style(style);
|
||||||
|
}
|
||||||
|
|
||||||
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
|
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
|
||||||
egui::menu::bar(ui, |ui| {
|
egui::menu::bar(ui, |ui| {
|
||||||
ui.menu_button("File", |ui| {
|
ui.menu_button("File", |ui| {
|
||||||
|
if ui.button("Show config").clicked() {
|
||||||
|
view_state.show_config = !view_state.show_config;
|
||||||
|
}
|
||||||
if ui.button("Quit").clicked() {
|
if ui.button("Quit").clicked() {
|
||||||
frame.close();
|
frame.close();
|
||||||
}
|
}
|
||||||
if ui.button("Show config").clicked() {
|
});
|
||||||
view_state.show_config = !view_state.show_config;
|
ui.menu_button("Tools", |ui| {
|
||||||
|
if ui.button("Demangle").clicked() {
|
||||||
|
view_state.show_demangle = !view_state.show_demangle;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -221,31 +318,55 @@ impl eframe::App for App {
|
||||||
}
|
}
|
||||||
|
|
||||||
egui::Window::new("Config").open(&mut view_state.show_config).show(ctx, |ui| {
|
egui::Window::new("Config").open(&mut view_state.show_config).show(ctx, |ui| {
|
||||||
ui.label("Diff type:");
|
ui.label("UI font:");
|
||||||
|
egui::introspection::font_id_ui(ui, &mut view_state.view_config.ui_font);
|
||||||
if egui::RadioButton::new(
|
|
||||||
view_state.diff_kind == DiffKind::SplitObj,
|
|
||||||
"Split object diff",
|
|
||||||
)
|
|
||||||
.ui(ui)
|
|
||||||
.on_hover_text("Compare individual object files")
|
|
||||||
.clicked()
|
|
||||||
{
|
|
||||||
view_state.diff_kind = DiffKind::SplitObj;
|
|
||||||
}
|
|
||||||
|
|
||||||
if egui::RadioButton::new(
|
|
||||||
view_state.diff_kind == DiffKind::WholeBinary,
|
|
||||||
"Whole binary diff",
|
|
||||||
)
|
|
||||||
.ui(ui)
|
|
||||||
.on_hover_text("Compare two full binaries")
|
|
||||||
.clicked()
|
|
||||||
{
|
|
||||||
view_state.diff_kind = DiffKind::WholeBinary;
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
ui.label("Code font:");
|
||||||
|
egui::introspection::font_id_ui(ui, &mut view_state.view_config.code_font);
|
||||||
|
ui.separator();
|
||||||
|
ui.label("Diff colors:");
|
||||||
|
if ui.button("Reset").clicked() {
|
||||||
|
view_state.view_config.diff_colors = DEFAULT_COLOR_ROTATION.to_vec();
|
||||||
|
}
|
||||||
|
let mut remove_at: Option<usize> = None;
|
||||||
|
let num_colors = view_state.view_config.diff_colors.len();
|
||||||
|
for (idx, color) in view_state.view_config.diff_colors.iter_mut().enumerate() {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.color_edit_button_srgba(color);
|
||||||
|
if num_colors > 1 {
|
||||||
|
if ui.small_button("-").clicked() {
|
||||||
|
remove_at = Some(idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if let Some(idx) = remove_at {
|
||||||
|
view_state.view_config.diff_colors.remove(idx);
|
||||||
|
}
|
||||||
|
if ui.small_button("+").clicked() {
|
||||||
|
view_state.view_config.diff_colors.push(Color32::BLACK);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
egui::Window::new("Demangle").open(&mut view_state.show_demangle).show(ctx, |ui| {
|
||||||
|
ui.text_edit_singleline(&mut view_state.demangle_text);
|
||||||
|
ui.add_space(10.0);
|
||||||
|
if let Some(demangled) =
|
||||||
|
cwdemangle::demangle(&view_state.demangle_text, &Default::default())
|
||||||
|
{
|
||||||
|
ui.scope(|ui| {
|
||||||
|
ui.style_mut().override_text_style = Some(TextStyle::Monospace);
|
||||||
|
ui.colored_label(Color32::LIGHT_BLUE, &demangled);
|
||||||
|
});
|
||||||
|
if ui.button("Copy").clicked() {
|
||||||
|
ui.output().copied_text = demangled;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ui.scope(|ui| {
|
||||||
|
ui.style_mut().override_text_style = Some(TextStyle::Monospace);
|
||||||
|
ui.colored_label(Color32::LIGHT_RED, "[invalid]");
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Windows + request_repaint_after breaks dialogs:
|
// Windows + request_repaint_after breaks dialogs:
|
||||||
|
@ -272,7 +393,7 @@ impl eframe::App for App {
|
||||||
eframe::set_value(storage, eframe::APP_KEY, self);
|
eframe::set_value(storage, eframe::APP_KEY, self);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn post_rendering(&mut self, _window_size_px: [u32; 2], _frame: &Frame) {
|
fn post_rendering(&mut self, _window_size_px: [u32; 2], _frame: &eframe::Frame) {
|
||||||
for job in &mut self.view_state.jobs {
|
for job in &mut self.view_state.jobs {
|
||||||
if let Some(handle) = &job.handle {
|
if let Some(handle) = &job.handle {
|
||||||
if !handle.is_finished() {
|
if !handle.is_finished() {
|
||||||
|
@ -305,10 +426,38 @@ impl eframe::App for App {
|
||||||
time: OffsetDateTime::now_utc(),
|
time: OffsetDateTime::now_utc(),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
JobResult::CheckUpdate(state) => {
|
||||||
|
self.view_state.check_update = Some(state);
|
||||||
|
}
|
||||||
|
JobResult::Update(state) => {
|
||||||
|
if let Ok(mut guard) = self.relaunch_path.lock() {
|
||||||
|
*guard = Some(state.exe_path);
|
||||||
|
}
|
||||||
|
self.should_relaunch = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(err) => {
|
||||||
log::error!("Failed to join job handle: {:?}", e);
|
let err = if let Some(msg) = err.downcast_ref::<&'static str>() {
|
||||||
|
anyhow::Error::msg(*msg)
|
||||||
|
} else if let Some(msg) = err.downcast_ref::<String>() {
|
||||||
|
anyhow::Error::msg(msg.clone())
|
||||||
|
} else {
|
||||||
|
anyhow::Error::msg("Thread panicked")
|
||||||
|
};
|
||||||
|
let result = job.status.write();
|
||||||
|
if let Ok(mut guard) = result {
|
||||||
|
guard.error = Some(err);
|
||||||
|
} else {
|
||||||
|
drop(result);
|
||||||
|
job.status = Arc::new(RwLock::new(JobStatus {
|
||||||
|
title: "Error".to_string(),
|
||||||
|
progress_percent: 0.0,
|
||||||
|
progress_items: None,
|
||||||
|
status: "".to_string(),
|
||||||
|
error: Some(err),
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -355,6 +504,11 @@ impl eframe::App for App {
|
||||||
}
|
}
|
||||||
self.modified.store(false, Ordering::Relaxed);
|
self.modified.store(false, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if config.queue_update_check {
|
||||||
|
self.view_state.jobs.push(queue_check_update());
|
||||||
|
config.queue_update_check = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
183
src/diff.rs
183
src/diff.rs
|
@ -12,6 +12,30 @@ use crate::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fn no_diff_code(
|
||||||
|
arch: ObjArchitecture,
|
||||||
|
data: &Vec<u8>,
|
||||||
|
symbol: &mut ObjSymbol,
|
||||||
|
relocs: &Vec<ObjReloc>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let code =
|
||||||
|
&data[symbol.section_address as usize..(symbol.section_address + symbol.size) as usize];
|
||||||
|
let (_, ins) = match arch {
|
||||||
|
ObjArchitecture::PowerPc => ppc::process_code(code, symbol.address, relocs)?,
|
||||||
|
ObjArchitecture::Mips => {
|
||||||
|
mips::process_code(code, symbol.address, symbol.address + symbol.size, relocs)?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut diff = Vec::<ObjInsDiff>::new();
|
||||||
|
for i in ins {
|
||||||
|
diff.push(ObjInsDiff { ins: Some(i), kind: ObjInsDiffKind::None, ..Default::default() });
|
||||||
|
}
|
||||||
|
resolve_branches(&mut diff);
|
||||||
|
symbol.instructions = diff;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn diff_code(
|
pub fn diff_code(
|
||||||
arch: ObjArchitecture,
|
arch: ObjArchitecture,
|
||||||
left_data: &[u8],
|
left_data: &[u8],
|
||||||
|
@ -133,8 +157,8 @@ pub fn diff_code(
|
||||||
} else {
|
} else {
|
||||||
((total - diff_state.diff_count) as f32 / total as f32) * 100.0
|
((total - diff_state.diff_count) as f32 / total as f32) * 100.0
|
||||||
};
|
};
|
||||||
left_symbol.match_percent = percent;
|
left_symbol.match_percent = Some(percent);
|
||||||
right_symbol.match_percent = percent;
|
right_symbol.match_percent = Some(percent);
|
||||||
|
|
||||||
left_symbol.instructions = left_diff;
|
left_symbol.instructions = left_diff;
|
||||||
right_symbol.instructions = right_diff;
|
right_symbol.instructions = right_diff;
|
||||||
|
@ -356,13 +380,101 @@ pub fn diff_objs(left: &mut ObjInfo, right: &mut ObjInfo, _diff_config: &DiffCon
|
||||||
&left_section.relocations,
|
&left_section.relocations,
|
||||||
&right_section.relocations,
|
&right_section.relocations,
|
||||||
)?;
|
)?;
|
||||||
|
} else {
|
||||||
|
no_diff_code(
|
||||||
|
left.architecture,
|
||||||
|
&left_section.data,
|
||||||
|
left_symbol,
|
||||||
|
&left_section.relocations,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for right_symbol in &mut right_section.symbols {
|
||||||
|
if right_symbol.instructions.is_empty() {
|
||||||
|
no_diff_code(
|
||||||
|
left.architecture,
|
||||||
|
&right_section.data,
|
||||||
|
right_symbol,
|
||||||
|
&right_section.relocations,
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if left_section.kind == ObjSectionKind::Data {
|
} else if left_section.kind == ObjSectionKind::Data {
|
||||||
diff_data(left_section, right_section);
|
diff_data(left_section, right_section);
|
||||||
|
// diff_data_symbols(left_section, right_section)?;
|
||||||
|
} else if left_section.kind == ObjSectionKind::Bss {
|
||||||
|
diff_bss_symbols(&mut left_section.symbols, &mut right_section.symbols)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
diff_bss_symbols(&mut left.common, &mut right.common)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn diff_bss_symbols(left_symbols: &mut [ObjSymbol], right_symbols: &mut [ObjSymbol]) -> Result<()> {
|
||||||
|
for left_symbol in left_symbols {
|
||||||
|
if let Some(right_symbol) = find_symbol(right_symbols, &left_symbol.name) {
|
||||||
|
left_symbol.diff_symbol = Some(right_symbol.name.clone());
|
||||||
|
right_symbol.diff_symbol = Some(left_symbol.name.clone());
|
||||||
|
let percent = if left_symbol.size == right_symbol.size { 100.0 } else { 50.0 };
|
||||||
|
left_symbol.match_percent = Some(percent);
|
||||||
|
right_symbol.match_percent = Some(percent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// WIP diff-by-symbol
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn diff_data_symbols(left: &mut ObjSection, right: &mut ObjSection) -> Result<()> {
|
||||||
|
let mut left_ops = Vec::<u32>::with_capacity(left.symbols.len());
|
||||||
|
let mut right_ops = Vec::<u32>::with_capacity(right.symbols.len());
|
||||||
|
for left_symbol in &left.symbols {
|
||||||
|
let data = &left.data
|
||||||
|
[left_symbol.address as usize..(left_symbol.address + left_symbol.size) as usize];
|
||||||
|
let hash = twox_hash::xxh3::hash64(data);
|
||||||
|
left_ops.push(hash as u32);
|
||||||
|
}
|
||||||
|
for symbol in &right.symbols {
|
||||||
|
let data = &right.data[symbol.address as usize..(symbol.address + symbol.size) as usize];
|
||||||
|
let hash = twox_hash::xxh3::hash64(data);
|
||||||
|
right_ops.push(hash as u32);
|
||||||
|
}
|
||||||
|
|
||||||
|
let edit_ops = editops_find(&left_ops, &right_ops);
|
||||||
|
if edit_ops.is_empty() && !left.data.is_empty() {
|
||||||
|
let mut left_iter = left.symbols.iter_mut();
|
||||||
|
let mut right_iter = right.symbols.iter_mut();
|
||||||
|
loop {
|
||||||
|
let (left_symbol, right_symbol) = match (left_iter.next(), right_iter.next()) {
|
||||||
|
(Some(l), Some(r)) => (l, r),
|
||||||
|
(None, None) => break,
|
||||||
|
_ => return Err(anyhow::Error::msg("L/R mismatch in diff_data_symbols")),
|
||||||
|
};
|
||||||
|
let left_data = &left.data
|
||||||
|
[left_symbol.address as usize..(left_symbol.address + left_symbol.size) as usize];
|
||||||
|
let right_data = &right.data[right_symbol.address as usize
|
||||||
|
..(right_symbol.address + right_symbol.size) as usize];
|
||||||
|
|
||||||
|
left.data_diff.push(ObjDataDiff {
|
||||||
|
data: left_data.to_vec(),
|
||||||
|
kind: ObjDataDiffKind::None,
|
||||||
|
len: left_symbol.size as usize,
|
||||||
|
symbol: left_symbol.name.clone(),
|
||||||
|
});
|
||||||
|
right.data_diff.push(ObjDataDiff {
|
||||||
|
data: right_data.to_vec(),
|
||||||
|
kind: ObjDataDiffKind::None,
|
||||||
|
len: right_symbol.size as usize,
|
||||||
|
symbol: right_symbol.name.clone(),
|
||||||
|
});
|
||||||
|
left_symbol.diff_symbol = Some(right_symbol.name.clone());
|
||||||
|
left_symbol.match_percent = Some(100.0);
|
||||||
|
right_symbol.diff_symbol = Some(left_symbol.name.clone());
|
||||||
|
right_symbol.match_percent = Some(100.0);
|
||||||
|
}
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -373,11 +485,13 @@ fn diff_data(left: &mut ObjSection, right: &mut ObjSection) {
|
||||||
data: left.data.clone(),
|
data: left.data.clone(),
|
||||||
kind: ObjDataDiffKind::None,
|
kind: ObjDataDiffKind::None,
|
||||||
len: left.data.len(),
|
len: left.data.len(),
|
||||||
|
symbol: String::new(),
|
||||||
}];
|
}];
|
||||||
right.data_diff = vec![ObjDataDiff {
|
right.data_diff = vec![ObjDataDiff {
|
||||||
data: right.data.clone(),
|
data: right.data.clone(),
|
||||||
kind: ObjDataDiffKind::None,
|
kind: ObjDataDiffKind::None,
|
||||||
len: right.data.len(),
|
len: right.data.len(),
|
||||||
|
symbol: String::new(),
|
||||||
}];
|
}];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -390,23 +504,7 @@ fn diff_data(left: &mut ObjSection, right: &mut ObjSection) {
|
||||||
let mut cur_left_data = Vec::<u8>::new();
|
let mut cur_left_data = Vec::<u8>::new();
|
||||||
let mut cur_right_data = Vec::<u8>::new();
|
let mut cur_right_data = Vec::<u8>::new();
|
||||||
for op in edit_ops {
|
for op in edit_ops {
|
||||||
if left_cur < op.first_start {
|
if cur_op != op.op_type || left_cur < op.first_start || right_cur < op.second_start {
|
||||||
left_diff.push(ObjDataDiff {
|
|
||||||
data: left.data[left_cur..op.first_start].to_vec(),
|
|
||||||
kind: ObjDataDiffKind::None,
|
|
||||||
len: op.first_start - left_cur,
|
|
||||||
});
|
|
||||||
left_cur = op.first_start;
|
|
||||||
}
|
|
||||||
if right_cur < op.second_start {
|
|
||||||
right_diff.push(ObjDataDiff {
|
|
||||||
data: right.data[right_cur..op.second_start].to_vec(),
|
|
||||||
kind: ObjDataDiffKind::None,
|
|
||||||
len: op.second_start - right_cur,
|
|
||||||
});
|
|
||||||
right_cur = op.second_start;
|
|
||||||
}
|
|
||||||
if cur_op != op.op_type {
|
|
||||||
match cur_op {
|
match cur_op {
|
||||||
LevEditType::Keep => {}
|
LevEditType::Keep => {}
|
||||||
LevEditType::Replace => {
|
LevEditType::Replace => {
|
||||||
|
@ -418,11 +516,13 @@ fn diff_data(left: &mut ObjSection, right: &mut ObjSection) {
|
||||||
data: left_data,
|
data: left_data,
|
||||||
kind: ObjDataDiffKind::Replace,
|
kind: ObjDataDiffKind::Replace,
|
||||||
len: left_data_len,
|
len: left_data_len,
|
||||||
|
symbol: String::new(),
|
||||||
});
|
});
|
||||||
right_diff.push(ObjDataDiff {
|
right_diff.push(ObjDataDiff {
|
||||||
data: right_data,
|
data: right_data,
|
||||||
kind: ObjDataDiffKind::Replace,
|
kind: ObjDataDiffKind::Replace,
|
||||||
len: right_data_len,
|
len: right_data_len,
|
||||||
|
symbol: String::new(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
LevEditType::Insert => {
|
LevEditType::Insert => {
|
||||||
|
@ -432,11 +532,13 @@ fn diff_data(left: &mut ObjSection, right: &mut ObjSection) {
|
||||||
data: vec![],
|
data: vec![],
|
||||||
kind: ObjDataDiffKind::Insert,
|
kind: ObjDataDiffKind::Insert,
|
||||||
len: right_data_len,
|
len: right_data_len,
|
||||||
|
symbol: String::new(),
|
||||||
});
|
});
|
||||||
right_diff.push(ObjDataDiff {
|
right_diff.push(ObjDataDiff {
|
||||||
data: right_data,
|
data: right_data,
|
||||||
kind: ObjDataDiffKind::Insert,
|
kind: ObjDataDiffKind::Insert,
|
||||||
len: right_data_len,
|
len: right_data_len,
|
||||||
|
symbol: String::new(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
LevEditType::Delete => {
|
LevEditType::Delete => {
|
||||||
|
@ -446,15 +548,35 @@ fn diff_data(left: &mut ObjSection, right: &mut ObjSection) {
|
||||||
data: left_data,
|
data: left_data,
|
||||||
kind: ObjDataDiffKind::Delete,
|
kind: ObjDataDiffKind::Delete,
|
||||||
len: left_data_len,
|
len: left_data_len,
|
||||||
|
symbol: String::new(),
|
||||||
});
|
});
|
||||||
right_diff.push(ObjDataDiff {
|
right_diff.push(ObjDataDiff {
|
||||||
data: vec![],
|
data: vec![],
|
||||||
kind: ObjDataDiffKind::Delete,
|
kind: ObjDataDiffKind::Delete,
|
||||||
len: left_data_len,
|
len: left_data_len,
|
||||||
|
symbol: String::new(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if left_cur < op.first_start {
|
||||||
|
left_diff.push(ObjDataDiff {
|
||||||
|
data: left.data[left_cur..op.first_start].to_vec(),
|
||||||
|
kind: ObjDataDiffKind::None,
|
||||||
|
len: op.first_start - left_cur,
|
||||||
|
symbol: String::new(),
|
||||||
|
});
|
||||||
|
left_cur = op.first_start;
|
||||||
|
}
|
||||||
|
if right_cur < op.second_start {
|
||||||
|
right_diff.push(ObjDataDiff {
|
||||||
|
data: right.data[right_cur..op.second_start].to_vec(),
|
||||||
|
kind: ObjDataDiffKind::None,
|
||||||
|
len: op.second_start - right_cur,
|
||||||
|
symbol: String::new(),
|
||||||
|
});
|
||||||
|
right_cur = op.second_start;
|
||||||
|
}
|
||||||
match op.op_type {
|
match op.op_type {
|
||||||
LevEditType::Replace => {
|
LevEditType::Replace => {
|
||||||
cur_left_data.push(left.data[left_cur]);
|
cur_left_data.push(left.data[left_cur]);
|
||||||
|
@ -504,11 +626,13 @@ fn diff_data(left: &mut ObjSection, right: &mut ObjSection) {
|
||||||
data: left_data,
|
data: left_data,
|
||||||
kind: ObjDataDiffKind::Replace,
|
kind: ObjDataDiffKind::Replace,
|
||||||
len: left_data_len,
|
len: left_data_len,
|
||||||
|
symbol: String::new(),
|
||||||
});
|
});
|
||||||
right_diff.push(ObjDataDiff {
|
right_diff.push(ObjDataDiff {
|
||||||
data: right_data,
|
data: right_data,
|
||||||
kind: ObjDataDiffKind::Replace,
|
kind: ObjDataDiffKind::Replace,
|
||||||
len: right_data_len,
|
len: right_data_len,
|
||||||
|
symbol: String::new(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
LevEditType::Insert => {
|
LevEditType::Insert => {
|
||||||
|
@ -518,11 +642,13 @@ fn diff_data(left: &mut ObjSection, right: &mut ObjSection) {
|
||||||
data: vec![],
|
data: vec![],
|
||||||
kind: ObjDataDiffKind::Insert,
|
kind: ObjDataDiffKind::Insert,
|
||||||
len: right_data_len,
|
len: right_data_len,
|
||||||
|
symbol: String::new(),
|
||||||
});
|
});
|
||||||
right_diff.push(ObjDataDiff {
|
right_diff.push(ObjDataDiff {
|
||||||
data: right_data,
|
data: right_data,
|
||||||
kind: ObjDataDiffKind::Insert,
|
kind: ObjDataDiffKind::Insert,
|
||||||
len: right_data_len,
|
len: right_data_len,
|
||||||
|
symbol: String::new(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
LevEditType::Delete => {
|
LevEditType::Delete => {
|
||||||
|
@ -532,15 +658,34 @@ fn diff_data(left: &mut ObjSection, right: &mut ObjSection) {
|
||||||
data: left_data,
|
data: left_data,
|
||||||
kind: ObjDataDiffKind::Delete,
|
kind: ObjDataDiffKind::Delete,
|
||||||
len: left_data_len,
|
len: left_data_len,
|
||||||
|
symbol: String::new(),
|
||||||
});
|
});
|
||||||
right_diff.push(ObjDataDiff {
|
right_diff.push(ObjDataDiff {
|
||||||
data: vec![],
|
data: vec![],
|
||||||
kind: ObjDataDiffKind::Delete,
|
kind: ObjDataDiffKind::Delete,
|
||||||
len: left_data_len,
|
len: left_data_len,
|
||||||
|
symbol: String::new(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if left_cur < left.data.len() {
|
||||||
|
left_diff.push(ObjDataDiff {
|
||||||
|
data: left.data[left_cur..].to_vec(),
|
||||||
|
kind: ObjDataDiffKind::None,
|
||||||
|
len: left.data.len() - left_cur,
|
||||||
|
symbol: String::new(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if right_cur < right.data.len() {
|
||||||
|
right_diff.push(ObjDataDiff {
|
||||||
|
data: right.data[right_cur..].to_vec(),
|
||||||
|
kind: ObjDataDiffKind::None,
|
||||||
|
len: right.data.len() - right_cur,
|
||||||
|
symbol: String::new(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
left.data_diff = left_diff;
|
left.data_diff = left_diff;
|
||||||
right.data_diff = right_diff;
|
right.data_diff = right_diff;
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,8 @@ pub struct LevMatchingBlock {
|
||||||
pub len: usize,
|
pub len: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn editops_find(query: &[u8], choice: &[u8]) -> Vec<LevEditOp> {
|
pub fn editops_find<T>(query: &[T], choice: &[T]) -> Vec<LevEditOp>
|
||||||
|
where T: PartialEq {
|
||||||
let string_affix = Affix::find(query, choice);
|
let string_affix = Affix::find(query, choice);
|
||||||
|
|
||||||
let first_string_len = string_affix.first_string_len;
|
let first_string_len = string_affix.first_string_len;
|
||||||
|
@ -96,14 +97,17 @@ pub fn editops_find(query: &[u8], choice: &[u8]) -> Vec<LevEditOp> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn editops_from_cost_matrix(
|
fn editops_from_cost_matrix<T>(
|
||||||
string1: &[u8],
|
string1: &[T],
|
||||||
string2: &[u8],
|
string2: &[T],
|
||||||
len1: usize,
|
len1: usize,
|
||||||
len2: usize,
|
len2: usize,
|
||||||
prefix_len: usize,
|
prefix_len: usize,
|
||||||
cache_matrix: Vec<usize>,
|
cache_matrix: Vec<usize>,
|
||||||
) -> Vec<LevEditOp> {
|
) -> Vec<LevEditOp>
|
||||||
|
where
|
||||||
|
T: PartialEq,
|
||||||
|
{
|
||||||
let mut dir = 0;
|
let mut dir = 0;
|
||||||
|
|
||||||
let mut ops: Vec<LevEditOp> = vec![];
|
let mut ops: Vec<LevEditOp> = vec![];
|
||||||
|
@ -187,7 +191,8 @@ pub struct Affix {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Affix {
|
impl Affix {
|
||||||
pub fn find(first_string: &[u8], second_string: &[u8]) -> Affix {
|
pub fn find<T>(first_string: &[T], second_string: &[T]) -> Affix
|
||||||
|
where T: PartialEq {
|
||||||
// remove common prefix and suffix (linear vs square runtime for levensthein)
|
// remove common prefix and suffix (linear vs square runtime for levensthein)
|
||||||
let mut first_iter = first_string.iter();
|
let mut first_iter = first_string.iter();
|
||||||
let mut second_iter = second_string.iter();
|
let mut second_iter = second_string.iter();
|
||||||
|
|
|
@ -38,7 +38,7 @@ fn run_build(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn queue_bindiff(config: Arc<RwLock<AppConfig>>) -> JobState {
|
pub fn queue_bindiff(config: Arc<RwLock<AppConfig>>) -> JobState {
|
||||||
queue_job(Job::BinDiff, move |status, cancel| {
|
queue_job("Binary diff", Job::BinDiff, move |status, cancel| {
|
||||||
run_build(status, cancel, config).map(JobResult::BinDiff)
|
run_build(status, cancel, config).map(JobResult::BinDiff)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
use std::sync::mpsc::Receiver;
|
||||||
|
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use self_update::{cargo_crate_version, update::Release};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
jobs::{queue_job, update_status, Job, JobResult, JobState, Status},
|
||||||
|
update::{build_updater, BIN_NAME},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct CheckUpdateResult {
|
||||||
|
pub update_available: bool,
|
||||||
|
pub latest_release: Release,
|
||||||
|
pub found_binary: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_check_update(status: &Status, cancel: Receiver<()>) -> Result<Box<CheckUpdateResult>> {
|
||||||
|
update_status(status, "Fetching latest release".to_string(), 0, 1, &cancel)?;
|
||||||
|
let updater = build_updater().context("Failed to create release updater")?;
|
||||||
|
let latest_release = updater.get_latest_release()?;
|
||||||
|
let update_available =
|
||||||
|
self_update::version::bump_is_greater(cargo_crate_version!(), &latest_release.version)?;
|
||||||
|
let found_binary = latest_release.assets.iter().any(|a| a.name == BIN_NAME);
|
||||||
|
|
||||||
|
update_status(status, "Complete".to_string(), 1, 1, &cancel)?;
|
||||||
|
Ok(Box::new(CheckUpdateResult { update_available, latest_release, found_binary }))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn queue_check_update() -> JobState {
|
||||||
|
queue_job("Check for updates", Job::CheckUpdate, move |status, cancel| {
|
||||||
|
run_check_update(status, cancel).map(JobResult::CheckUpdate)
|
||||||
|
})
|
||||||
|
}
|
|
@ -9,15 +9,22 @@ use std::{
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
use crate::jobs::{bindiff::BinDiffResult, objdiff::ObjDiffResult};
|
use crate::jobs::{
|
||||||
|
bindiff::BinDiffResult, check_update::CheckUpdateResult, objdiff::ObjDiffResult,
|
||||||
|
update::UpdateResult,
|
||||||
|
};
|
||||||
|
|
||||||
pub mod bindiff;
|
pub mod bindiff;
|
||||||
|
pub mod check_update;
|
||||||
pub mod objdiff;
|
pub mod objdiff;
|
||||||
|
pub mod update;
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
|
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
|
||||||
pub enum Job {
|
pub enum Job {
|
||||||
ObjDiff,
|
ObjDiff,
|
||||||
BinDiff,
|
BinDiff,
|
||||||
|
CheckUpdate,
|
||||||
|
Update,
|
||||||
}
|
}
|
||||||
pub static JOB_ID: AtomicUsize = AtomicUsize::new(0);
|
pub static JOB_ID: AtomicUsize = AtomicUsize::new(0);
|
||||||
pub struct JobState {
|
pub struct JobState {
|
||||||
|
@ -40,6 +47,8 @@ pub enum JobResult {
|
||||||
None,
|
None,
|
||||||
ObjDiff(Box<ObjDiffResult>),
|
ObjDiff(Box<ObjDiffResult>),
|
||||||
BinDiff(Box<BinDiffResult>),
|
BinDiff(Box<BinDiffResult>),
|
||||||
|
CheckUpdate(Box<CheckUpdateResult>),
|
||||||
|
Update(Box<UpdateResult>),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn should_cancel(rx: &Receiver<()>) -> bool {
|
fn should_cancel(rx: &Receiver<()>) -> bool {
|
||||||
|
@ -52,14 +61,15 @@ fn should_cancel(rx: &Receiver<()>) -> bool {
|
||||||
type Status = Arc<RwLock<JobStatus>>;
|
type Status = Arc<RwLock<JobStatus>>;
|
||||||
|
|
||||||
fn queue_job(
|
fn queue_job(
|
||||||
|
title: &str,
|
||||||
job_type: Job,
|
job_type: Job,
|
||||||
run: impl FnOnce(&Status, Receiver<()>) -> Result<JobResult> + Send + 'static,
|
run: impl FnOnce(&Status, Receiver<()>) -> Result<JobResult> + Send + 'static,
|
||||||
) -> JobState {
|
) -> JobState {
|
||||||
let status = Arc::new(RwLock::new(JobStatus {
|
let status = Arc::new(RwLock::new(JobStatus {
|
||||||
title: String::new(),
|
title: title.to_string(),
|
||||||
progress_percent: 0.0,
|
progress_percent: 0.0,
|
||||||
progress_items: None,
|
progress_items: None,
|
||||||
status: "".to_string(),
|
status: String::new(),
|
||||||
error: None,
|
error: None,
|
||||||
}));
|
}));
|
||||||
let status_clone = status.clone();
|
let status_clone = status.clone();
|
||||||
|
|
|
@ -19,6 +19,7 @@ pub struct BuildStatus {
|
||||||
pub success: bool,
|
pub success: bool,
|
||||||
pub log: String,
|
pub log: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ObjDiffResult {
|
pub struct ObjDiffResult {
|
||||||
pub first_status: BuildStatus,
|
pub first_status: BuildStatus,
|
||||||
pub second_status: BuildStatus,
|
pub second_status: BuildStatus,
|
||||||
|
@ -29,7 +30,7 @@ pub struct ObjDiffResult {
|
||||||
|
|
||||||
fn run_make(cwd: &Path, arg: &Path, config: &AppConfig) -> BuildStatus {
|
fn run_make(cwd: &Path, arg: &Path, config: &AppConfig) -> BuildStatus {
|
||||||
match (|| -> Result<BuildStatus> {
|
match (|| -> Result<BuildStatus> {
|
||||||
let make = if config.custom_make.is_empty() { "make" } else { &config.custom_make };
|
let make = config.custom_make.as_deref().unwrap_or("make");
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
let mut command = {
|
let mut command = {
|
||||||
let mut command = Command::new(make);
|
let mut command = Command::new(make);
|
||||||
|
@ -136,7 +137,7 @@ fn run_build(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn queue_build(config: Arc<RwLock<AppConfig>>, diff_config: DiffConfig) -> JobState {
|
pub fn queue_build(config: Arc<RwLock<AppConfig>>, diff_config: DiffConfig) -> JobState {
|
||||||
queue_job(Job::ObjDiff, move |status, cancel| {
|
queue_job("Object diff", Job::ObjDiff, move |status, cancel| {
|
||||||
run_build(status, cancel, config, diff_config).map(JobResult::ObjDiff)
|
run_build(status, cancel, config, diff_config).map(JobResult::ObjDiff)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
use std::{
|
||||||
|
env::{current_dir, current_exe},
|
||||||
|
fs,
|
||||||
|
fs::File,
|
||||||
|
path::PathBuf,
|
||||||
|
sync::mpsc::Receiver,
|
||||||
|
};
|
||||||
|
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use const_format::formatcp;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
jobs::{queue_job, update_status, Job, JobResult, JobState, Status},
|
||||||
|
update::{build_updater, BIN_NAME},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct UpdateResult {
|
||||||
|
pub exe_path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_update(status: &Status, cancel: Receiver<()>) -> Result<Box<UpdateResult>> {
|
||||||
|
update_status(status, "Fetching latest release".to_string(), 0, 3, &cancel)?;
|
||||||
|
let updater = build_updater().context("Failed to create release updater")?;
|
||||||
|
let latest_release = updater.get_latest_release()?;
|
||||||
|
let asset = latest_release
|
||||||
|
.assets
|
||||||
|
.iter()
|
||||||
|
.find(|a| a.name == BIN_NAME)
|
||||||
|
.ok_or(anyhow::Error::msg(formatcp!("No release asset for {}", BIN_NAME)))?;
|
||||||
|
|
||||||
|
update_status(status, "Downloading release".to_string(), 1, 3, &cancel)?;
|
||||||
|
let tmp_dir = tempfile::Builder::new().prefix("update").tempdir_in(current_dir()?)?;
|
||||||
|
let tmp_path = tmp_dir.path().join(&asset.name);
|
||||||
|
let tmp_file = File::create(&tmp_path)?;
|
||||||
|
self_update::Download::from_url(&asset.download_url)
|
||||||
|
.set_header(reqwest::header::ACCEPT, "application/octet-stream".parse()?)
|
||||||
|
.download_to(&tmp_file)?;
|
||||||
|
|
||||||
|
update_status(status, "Extracting release".to_string(), 2, 3, &cancel)?;
|
||||||
|
let tmp_file = tmp_dir.path().join("replacement_tmp");
|
||||||
|
let target_file = current_exe()?;
|
||||||
|
self_update::Move::from_source(&tmp_path)
|
||||||
|
.replace_using_temp(&tmp_file)
|
||||||
|
.to_dest(&target_file)?;
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
use std::os::unix::fs::PermissionsExt;
|
||||||
|
let mut perms = fs::metadata(&target_file)?.permissions();
|
||||||
|
perms.set_mode(0755);
|
||||||
|
fs::set_permissions(&target_file, perms)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
update_status(status, "Complete".to_string(), 3, 3, &cancel)?;
|
||||||
|
Ok(Box::from(UpdateResult { exe_path: target_file }))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn queue_update() -> JobState {
|
||||||
|
queue_job("Update app", Job::Update, move |status, cancel| {
|
||||||
|
run_update(status, cancel).map(JobResult::Update)
|
||||||
|
})
|
||||||
|
}
|
|
@ -7,4 +7,5 @@ mod diff;
|
||||||
mod editops;
|
mod editops;
|
||||||
mod jobs;
|
mod jobs;
|
||||||
mod obj;
|
mod obj;
|
||||||
|
mod update;
|
||||||
mod views;
|
mod views;
|
||||||
|
|
32
src/main.rs
32
src/main.rs
|
@ -1,6 +1,9 @@
|
||||||
#![warn(clippy::all, rust_2018_idioms)]
|
#![warn(clippy::all, rust_2018_idioms)]
|
||||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||||
|
|
||||||
|
use std::{path::PathBuf, rc::Rc, sync::Mutex};
|
||||||
|
|
||||||
|
use cfg_if::cfg_if;
|
||||||
use time::UtcOffset;
|
use time::UtcOffset;
|
||||||
|
|
||||||
// When compiling natively:
|
// When compiling natively:
|
||||||
|
@ -12,15 +15,40 @@ fn main() {
|
||||||
// Because localtime_r is unsound in multithreaded apps,
|
// Because localtime_r is unsound in multithreaded apps,
|
||||||
// we must call this before initializing eframe.
|
// we must call this before initializing eframe.
|
||||||
// https://github.com/time-rs/time/issues/293
|
// https://github.com/time-rs/time/issues/293
|
||||||
let utc_offset = UtcOffset::current_local_offset().unwrap();
|
let utc_offset = UtcOffset::current_local_offset().unwrap_or(UtcOffset::UTC);
|
||||||
|
|
||||||
|
let exec_path: Rc<Mutex<Option<PathBuf>>> = Rc::new(Mutex::new(None));
|
||||||
|
let exec_path_clone = exec_path.clone();
|
||||||
let native_options = eframe::NativeOptions::default();
|
let native_options = eframe::NativeOptions::default();
|
||||||
// native_options.renderer = eframe::Renderer::Wgpu;
|
// native_options.renderer = eframe::Renderer::Wgpu;
|
||||||
eframe::run_native(
|
eframe::run_native(
|
||||||
"objdiff",
|
"objdiff",
|
||||||
native_options,
|
native_options,
|
||||||
Box::new(move |cc| Box::new(objdiff::App::new(cc, utc_offset))),
|
Box::new(move |cc| Box::new(objdiff::App::new(cc, utc_offset, exec_path_clone))),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Attempt to relaunch application from the updated path
|
||||||
|
if let Ok(mut guard) = exec_path.lock() {
|
||||||
|
if let Some(path) = guard.take() {
|
||||||
|
cfg_if! {
|
||||||
|
if #[cfg(unix)] {
|
||||||
|
let result = exec::Command::new(path)
|
||||||
|
.args(&std::env::args().collect::<Vec<String>>())
|
||||||
|
.exec();
|
||||||
|
eprintln!("Failed to relaunch: {:?}", result);
|
||||||
|
} else {
|
||||||
|
let result = std::process::Command::new(path)
|
||||||
|
.args(std::env::args())
|
||||||
|
.spawn()
|
||||||
|
.unwrap()
|
||||||
|
.wait();
|
||||||
|
if let Err(e) = result {
|
||||||
|
eprintln!("Failed to relaunch: {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// when compiling to web using trunk.
|
// when compiling to web using trunk.
|
||||||
|
|
|
@ -63,7 +63,7 @@ fn to_obj_symbol(obj_file: &File<'_>, symbol: &Symbol<'_, '_>, addend: i64) -> R
|
||||||
addend,
|
addend,
|
||||||
diff_symbol: None,
|
diff_symbol: None,
|
||||||
instructions: vec![],
|
instructions: vec![],
|
||||||
match_percent: 0.0,
|
match_percent: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,7 +81,7 @@ fn filter_sections(obj_file: &File<'_>) -> Result<Vec<ObjSection>> {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let name = section.name().context("Failed to process section name")?;
|
let name = section.name().context("Failed to process section name")?;
|
||||||
let data = section.data().context("Failed to read section data")?;
|
let data = section.uncompressed_data().context("Failed to read section data")?;
|
||||||
result.push(ObjSection {
|
result.push(ObjSection {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
kind: to_obj_section_kind(section.kind()),
|
kind: to_obj_section_kind(section.kind()),
|
||||||
|
@ -183,7 +183,7 @@ fn find_section_symbol(
|
||||||
addend: offset_addr as i64,
|
addend: offset_addr as i64,
|
||||||
diff_symbol: None,
|
diff_symbol: None,
|
||||||
instructions: vec![],
|
instructions: vec![],
|
||||||
match_percent: 0.0,
|
match_percent: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -290,8 +290,11 @@ fn relocations_by_section(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read(obj_path: &Path) -> Result<ObjInfo> {
|
pub fn read(obj_path: &Path) -> Result<ObjInfo> {
|
||||||
let bin_data = fs::read(obj_path)?;
|
let data = {
|
||||||
let obj_file = File::parse(&*bin_data)?;
|
let file = fs::File::open(obj_path)?;
|
||||||
|
unsafe { memmap2::Mmap::map(&file) }?
|
||||||
|
};
|
||||||
|
let obj_file = File::parse(&*data)?;
|
||||||
let architecture = match obj_file.architecture() {
|
let architecture = match obj_file.architecture() {
|
||||||
Architecture::PowerPc => ObjArchitecture::PowerPc,
|
Architecture::PowerPc => ObjArchitecture::PowerPc,
|
||||||
Architecture::Mips => ObjArchitecture::Mips,
|
Architecture::Mips => ObjArchitecture::Mips,
|
||||||
|
|
|
@ -109,6 +109,7 @@ pub struct ObjDataDiff {
|
||||||
pub data: Vec<u8>,
|
pub data: Vec<u8>,
|
||||||
pub kind: ObjDataDiffKind,
|
pub kind: ObjDataDiffKind,
|
||||||
pub len: usize,
|
pub len: usize,
|
||||||
|
pub symbol: String,
|
||||||
}
|
}
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ObjSymbol {
|
pub struct ObjSymbol {
|
||||||
|
@ -124,7 +125,7 @@ pub struct ObjSymbol {
|
||||||
// Diff
|
// Diff
|
||||||
pub diff_symbol: Option<String>,
|
pub diff_symbol: Option<String>,
|
||||||
pub instructions: Vec<ObjInsDiff>,
|
pub instructions: Vec<ObjInsDiff>,
|
||||||
pub match_percent: f32,
|
pub match_percent: Option<f32>,
|
||||||
}
|
}
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub enum ObjArchitecture {
|
pub enum ObjArchitecture {
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
use cfg_if::cfg_if;
|
||||||
|
use const_format::formatcp;
|
||||||
|
use self_update::{cargo_crate_version, update::ReleaseUpdate};
|
||||||
|
|
||||||
|
pub const OS: &str = std::env::consts::OS;
|
||||||
|
cfg_if! {
|
||||||
|
if #[cfg(target_arch = "aarch64")] {
|
||||||
|
cfg_if! {
|
||||||
|
if #[cfg(any(windows, target_os = "macos"))] {
|
||||||
|
pub const ARCH: &str = "arm64";
|
||||||
|
} else {
|
||||||
|
pub const ARCH: &str = std::env::consts::ARCH;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if #[cfg(target_arch = "arm")] {
|
||||||
|
pub const ARCH: &str = "armv7l";
|
||||||
|
} else {
|
||||||
|
pub const ARCH: &str = std::env::consts::ARCH;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub const GITHUB_USER: &str = "encounter";
|
||||||
|
pub const GITHUB_REPO: &str = "objdiff";
|
||||||
|
pub const BIN_NAME: &str =
|
||||||
|
formatcp!("{}-{}-{}{}", GITHUB_REPO, OS, ARCH, std::env::consts::EXE_SUFFIX);
|
||||||
|
pub const RELEASE_URL: &str =
|
||||||
|
formatcp!("https://github.com/{}/{}/releases/latest", GITHUB_USER, GITHUB_REPO);
|
||||||
|
|
||||||
|
pub fn build_updater() -> self_update::errors::Result<Box<dyn ReleaseUpdate>> {
|
||||||
|
self_update::backends::github::Update::configure()
|
||||||
|
.repo_owner(GITHUB_USER)
|
||||||
|
.repo_name(GITHUB_REPO)
|
||||||
|
.bin_name(BIN_NAME)
|
||||||
|
.no_confirm(true)
|
||||||
|
.show_output(false)
|
||||||
|
.current_version(cargo_crate_version!())
|
||||||
|
.build()
|
||||||
|
}
|
|
@ -4,10 +4,14 @@ use std::sync::{Arc, RwLock};
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
|
use const_format::formatcp;
|
||||||
|
use egui::{output::OpenUrl, Color32};
|
||||||
|
use self_update::cargo_crate_version;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::{AppConfig, DiffKind, ViewState},
|
app::{AppConfig, DiffKind, ViewState},
|
||||||
jobs::{bindiff::queue_bindiff, objdiff::queue_build},
|
jobs::{bindiff::queue_bindiff, objdiff::queue_build, update::queue_update},
|
||||||
|
update::RELEASE_URL,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
|
@ -57,8 +61,50 @@ pub fn config_ui(ui: &mut egui::Ui, config: &Arc<RwLock<AppConfig>>, view_state:
|
||||||
left_obj,
|
left_obj,
|
||||||
right_obj,
|
right_obj,
|
||||||
project_dir_change,
|
project_dir_change,
|
||||||
|
queue_update_check,
|
||||||
|
auto_update_check,
|
||||||
} = &mut *config_guard;
|
} = &mut *config_guard;
|
||||||
|
|
||||||
|
ui.heading("Updates");
|
||||||
|
ui.checkbox(auto_update_check, "Check for updates on startup");
|
||||||
|
if ui.button("Check now").clicked() {
|
||||||
|
*queue_update_check = true;
|
||||||
|
}
|
||||||
|
ui.label(format!("Current version: {}", cargo_crate_version!())).on_hover_ui_at_pointer(|ui| {
|
||||||
|
ui.label(formatcp!("Git branch: {}", env!("VERGEN_GIT_BRANCH")));
|
||||||
|
ui.label(formatcp!("Git commit: {}", env!("VERGEN_GIT_SHA")));
|
||||||
|
ui.label(formatcp!("Build target: {}", env!("VERGEN_CARGO_TARGET_TRIPLE")));
|
||||||
|
ui.label(formatcp!("Build type: {}", env!("VERGEN_CARGO_PROFILE")));
|
||||||
|
});
|
||||||
|
if let Some(state) = &view_state.check_update {
|
||||||
|
ui.label(format!("Latest version: {}", state.latest_release.version));
|
||||||
|
if state.update_available {
|
||||||
|
ui.colored_label(Color32::LIGHT_GREEN, "Update available");
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
if state.found_binary {
|
||||||
|
if ui
|
||||||
|
.button("Automatic")
|
||||||
|
.on_hover_text_at_pointer(
|
||||||
|
"Automatically download and replace the current build",
|
||||||
|
)
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
view_state.jobs.push(queue_update());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ui
|
||||||
|
.button("Manual")
|
||||||
|
.on_hover_text_at_pointer("Open a link to the latest release on GitHub")
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
ui.output().open_url =
|
||||||
|
Some(OpenUrl { url: RELEASE_URL.to_string(), new_tab: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ui.separator();
|
||||||
|
|
||||||
ui.heading("Build config");
|
ui.heading("Build config");
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
|
@ -82,7 +128,14 @@ pub fn config_ui(ui: &mut egui::Ui, config: &Arc<RwLock<AppConfig>>, view_state:
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.label("Custom make program:");
|
ui.label("Custom make program:");
|
||||||
ui.text_edit_singleline(custom_make);
|
let mut custom_make_str = custom_make.clone().unwrap_or_default();
|
||||||
|
if ui.text_edit_singleline(&mut custom_make_str).changed() {
|
||||||
|
if custom_make_str.is_empty() {
|
||||||
|
*custom_make = None;
|
||||||
|
} else {
|
||||||
|
*custom_make = Some(custom_make_str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
|
||||||
|
|
|
@ -5,10 +5,10 @@ use egui_extras::{Size, StripBuilder, TableBuilder};
|
||||||
use time::format_description;
|
use time::format_description;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::{View, ViewState},
|
app::{View, ViewConfig, ViewState},
|
||||||
jobs::Job,
|
jobs::Job,
|
||||||
obj::{ObjDataDiff, ObjDataDiffKind, ObjInfo, ObjSection},
|
obj::{ObjDataDiff, ObjDataDiffKind, ObjInfo, ObjSection},
|
||||||
views::{write_text, COLOR_RED, FONT_SIZE},
|
views::{write_text, COLOR_RED},
|
||||||
};
|
};
|
||||||
|
|
||||||
const BYTES_PER_ROW: usize = 16;
|
const BYTES_PER_ROW: usize = 16;
|
||||||
|
@ -17,12 +17,17 @@ fn find_section<'a>(obj: &'a ObjInfo, section_name: &str) -> Option<&'a ObjSecti
|
||||||
obj.sections.iter().find(|s| s.name == section_name)
|
obj.sections.iter().find(|s| s.name == section_name)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data_row_ui(ui: &mut egui::Ui, address: usize, diffs: &[ObjDataDiff]) {
|
fn data_row_ui(ui: &mut egui::Ui, address: usize, diffs: &[ObjDataDiff], config: &ViewConfig) {
|
||||||
if diffs.iter().any(|d| d.kind != ObjDataDiffKind::None) {
|
if diffs.iter().any(|d| d.kind != ObjDataDiffKind::None) {
|
||||||
ui.painter().rect_filled(ui.available_rect_before_wrap(), 0.0, ui.visuals().faint_bg_color);
|
ui.painter().rect_filled(ui.available_rect_before_wrap(), 0.0, ui.visuals().faint_bg_color);
|
||||||
}
|
}
|
||||||
let mut job = LayoutJob::default();
|
let mut job = LayoutJob::default();
|
||||||
write_text(format!("{:08X}: ", address).as_str(), Color32::GRAY, &mut job);
|
write_text(
|
||||||
|
format!("{:08X}: ", address).as_str(),
|
||||||
|
Color32::GRAY,
|
||||||
|
&mut job,
|
||||||
|
config.code_font.clone(),
|
||||||
|
);
|
||||||
let mut cur_addr = 0usize;
|
let mut cur_addr = 0usize;
|
||||||
for diff in diffs {
|
for diff in diffs {
|
||||||
let base_color = match diff.kind {
|
let base_color = match diff.kind {
|
||||||
|
@ -34,7 +39,7 @@ fn data_row_ui(ui: &mut egui::Ui, address: usize, diffs: &[ObjDataDiff]) {
|
||||||
if diff.data.is_empty() {
|
if diff.data.is_empty() {
|
||||||
let mut str = " ".repeat(diff.len);
|
let mut str = " ".repeat(diff.len);
|
||||||
str.push_str(" ".repeat(diff.len / 8).as_str());
|
str.push_str(" ".repeat(diff.len / 8).as_str());
|
||||||
write_text(str.as_str(), base_color, &mut job);
|
write_text(str.as_str(), base_color, &mut job, config.code_font.clone());
|
||||||
cur_addr += diff.len;
|
cur_addr += diff.len;
|
||||||
} else {
|
} else {
|
||||||
let mut text = String::new();
|
let mut text = String::new();
|
||||||
|
@ -45,7 +50,7 @@ fn data_row_ui(ui: &mut egui::Ui, address: usize, diffs: &[ObjDataDiff]) {
|
||||||
text.push(' ');
|
text.push(' ');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
write_text(text.as_str(), base_color, &mut job);
|
write_text(text.as_str(), base_color, &mut job, config.code_font.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if cur_addr < BYTES_PER_ROW {
|
if cur_addr < BYTES_PER_ROW {
|
||||||
|
@ -53,9 +58,9 @@ fn data_row_ui(ui: &mut egui::Ui, address: usize, diffs: &[ObjDataDiff]) {
|
||||||
let mut str = " ".to_string();
|
let mut str = " ".to_string();
|
||||||
str.push_str(" ".repeat(n).as_str());
|
str.push_str(" ".repeat(n).as_str());
|
||||||
str.push_str(" ".repeat(n / 8).as_str());
|
str.push_str(" ".repeat(n / 8).as_str());
|
||||||
write_text(str.as_str(), Color32::GRAY, &mut job);
|
write_text(str.as_str(), Color32::GRAY, &mut job, config.code_font.clone());
|
||||||
}
|
}
|
||||||
write_text(" ", Color32::GRAY, &mut job);
|
write_text(" ", Color32::GRAY, &mut job, config.code_font.clone());
|
||||||
for diff in diffs {
|
for diff in diffs {
|
||||||
let base_color = match diff.kind {
|
let base_color = match diff.kind {
|
||||||
ObjDataDiffKind::None => Color32::GRAY,
|
ObjDataDiffKind::None => Color32::GRAY,
|
||||||
|
@ -64,7 +69,12 @@ fn data_row_ui(ui: &mut egui::Ui, address: usize, diffs: &[ObjDataDiff]) {
|
||||||
ObjDataDiffKind::Insert => Color32::GREEN,
|
ObjDataDiffKind::Insert => Color32::GREEN,
|
||||||
};
|
};
|
||||||
if diff.data.is_empty() {
|
if diff.data.is_empty() {
|
||||||
write_text(" ".repeat(diff.len).as_str(), base_color, &mut job);
|
write_text(
|
||||||
|
" ".repeat(diff.len).as_str(),
|
||||||
|
base_color,
|
||||||
|
&mut job,
|
||||||
|
config.code_font.clone(),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
let mut text = String::new();
|
let mut text = String::new();
|
||||||
for byte in &diff.data {
|
for byte in &diff.data {
|
||||||
|
@ -75,7 +85,7 @@ fn data_row_ui(ui: &mut egui::Ui, address: usize, diffs: &[ObjDataDiff]) {
|
||||||
text.push('.');
|
text.push('.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
write_text(text.as_str(), base_color, &mut job);
|
write_text(text.as_str(), base_color, &mut job, config.code_font.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ui.add(Label::new(job).sense(Sense::click()));
|
ui.add(Label::new(job).sense(Sense::click()));
|
||||||
|
@ -101,6 +111,8 @@ fn split_diffs(diffs: &[ObjDataDiff]) -> Vec<Vec<ObjDataDiff>> {
|
||||||
},
|
},
|
||||||
kind: diff.kind,
|
kind: diff.kind,
|
||||||
len,
|
len,
|
||||||
|
// TODO
|
||||||
|
symbol: String::new(),
|
||||||
});
|
});
|
||||||
remaining_in_row -= len;
|
remaining_in_row -= len;
|
||||||
cur_len += len;
|
cur_len += len;
|
||||||
|
@ -121,6 +133,7 @@ fn data_table_ui(
|
||||||
left_obj: &ObjInfo,
|
left_obj: &ObjInfo,
|
||||||
right_obj: &ObjInfo,
|
right_obj: &ObjInfo,
|
||||||
section_name: &str,
|
section_name: &str,
|
||||||
|
config: &ViewConfig,
|
||||||
) -> Option<()> {
|
) -> Option<()> {
|
||||||
let left_section = find_section(left_obj, section_name)?;
|
let left_section = find_section(left_obj, section_name)?;
|
||||||
let right_section = find_section(right_obj, section_name)?;
|
let right_section = find_section(right_obj, section_name)?;
|
||||||
|
@ -135,13 +148,13 @@ fn data_table_ui(
|
||||||
let right_diffs = split_diffs(&right_section.data_diff);
|
let right_diffs = split_diffs(&right_section.data_diff);
|
||||||
|
|
||||||
table.body(|body| {
|
table.body(|body| {
|
||||||
body.rows(FONT_SIZE, total_rows, |row_index, mut row| {
|
body.rows(config.code_font.size, total_rows, |row_index, mut row| {
|
||||||
let address = row_index * BYTES_PER_ROW;
|
let address = row_index * BYTES_PER_ROW;
|
||||||
row.col(|ui| {
|
row.col(|ui| {
|
||||||
data_row_ui(ui, address, &left_diffs[row_index]);
|
data_row_ui(ui, address, &left_diffs[row_index], config);
|
||||||
});
|
});
|
||||||
row.col(|ui| {
|
row.col(|ui| {
|
||||||
data_row_ui(ui, address, &right_diffs[row_index]);
|
data_row_ui(ui, address, &right_diffs[row_index], config);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -233,7 +246,13 @@ pub fn data_diff_ui(ui: &mut egui::Ui, view_state: &mut ViewState) -> bool {
|
||||||
.column(Size::relative(0.5))
|
.column(Size::relative(0.5))
|
||||||
.column(Size::relative(0.5))
|
.column(Size::relative(0.5))
|
||||||
.resizable(false);
|
.resizable(false);
|
||||||
data_table_ui(table, left_obj, right_obj, selected_symbol);
|
data_table_ui(
|
||||||
|
table,
|
||||||
|
left_obj,
|
||||||
|
right_obj,
|
||||||
|
selected_symbol,
|
||||||
|
&view_state.view_config,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,62 +1,62 @@
|
||||||
use std::default::Default;
|
use std::default::Default;
|
||||||
|
|
||||||
use cwdemangle::demangle;
|
use cwdemangle::demangle;
|
||||||
use egui::{text::LayoutJob, Color32, Label, Sense};
|
use egui::{text::LayoutJob, Color32, FontId, Label, Sense};
|
||||||
use egui_extras::{Size, StripBuilder, TableBuilder};
|
use egui_extras::{Size, StripBuilder, TableBuilder};
|
||||||
use ppc750cl::Argument;
|
use ppc750cl::Argument;
|
||||||
use time::format_description;
|
use time::format_description;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::{View, ViewState},
|
app::{View, ViewConfig, ViewState},
|
||||||
jobs::Job,
|
jobs::Job,
|
||||||
obj::{
|
obj::{
|
||||||
ObjInfo, ObjIns, ObjInsArg, ObjInsArgDiff, ObjInsDiff, ObjInsDiffKind, ObjReloc,
|
ObjInfo, ObjIns, ObjInsArg, ObjInsArgDiff, ObjInsDiff, ObjInsDiffKind, ObjReloc,
|
||||||
ObjRelocKind, ObjSymbol,
|
ObjRelocKind, ObjSymbol,
|
||||||
},
|
},
|
||||||
views::{symbol_diff::match_color_for_symbol, write_text, COLOR_RED, FONT_SIZE},
|
views::{symbol_diff::match_color_for_symbol, write_text, COLOR_RED},
|
||||||
};
|
};
|
||||||
|
|
||||||
fn write_reloc_name(reloc: &ObjReloc, color: Color32, job: &mut LayoutJob) {
|
fn write_reloc_name(reloc: &ObjReloc, color: Color32, job: &mut LayoutJob, font_id: FontId) {
|
||||||
let name = reloc.target.demangled_name.as_ref().unwrap_or(&reloc.target.name);
|
let name = reloc.target.demangled_name.as_ref().unwrap_or(&reloc.target.name);
|
||||||
write_text(name, Color32::LIGHT_GRAY, job);
|
write_text(name, Color32::LIGHT_GRAY, job, font_id.clone());
|
||||||
if reloc.target.addend != 0 {
|
if reloc.target.addend != 0 {
|
||||||
write_text(&format!("+{:X}", reloc.target.addend), color, job);
|
write_text(&format!("+{:X}", reloc.target.addend), color, job, font_id.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_reloc(reloc: &ObjReloc, color: Color32, job: &mut LayoutJob) {
|
fn write_reloc(reloc: &ObjReloc, color: Color32, job: &mut LayoutJob, font_id: FontId) {
|
||||||
match reloc.kind {
|
match reloc.kind {
|
||||||
ObjRelocKind::PpcAddr16Lo => {
|
ObjRelocKind::PpcAddr16Lo => {
|
||||||
write_reloc_name(reloc, color, job);
|
write_reloc_name(reloc, color, job, font_id.clone());
|
||||||
write_text("@l", color, job);
|
write_text("@l", color, job, font_id.clone());
|
||||||
}
|
}
|
||||||
ObjRelocKind::PpcAddr16Hi => {
|
ObjRelocKind::PpcAddr16Hi => {
|
||||||
write_reloc_name(reloc, color, job);
|
write_reloc_name(reloc, color, job, font_id.clone());
|
||||||
write_text("@h", color, job);
|
write_text("@h", color, job, font_id.clone());
|
||||||
}
|
}
|
||||||
ObjRelocKind::PpcAddr16Ha => {
|
ObjRelocKind::PpcAddr16Ha => {
|
||||||
write_reloc_name(reloc, color, job);
|
write_reloc_name(reloc, color, job, font_id.clone());
|
||||||
write_text("@ha", color, job);
|
write_text("@ha", color, job, font_id.clone());
|
||||||
}
|
}
|
||||||
ObjRelocKind::PpcEmbSda21 => {
|
ObjRelocKind::PpcEmbSda21 => {
|
||||||
write_reloc_name(reloc, color, job);
|
write_reloc_name(reloc, color, job, font_id.clone());
|
||||||
write_text("@sda21", color, job);
|
write_text("@sda21", color, job, font_id.clone());
|
||||||
}
|
}
|
||||||
ObjRelocKind::MipsHi16 => {
|
ObjRelocKind::MipsHi16 => {
|
||||||
write_text("%hi(", color, job);
|
write_text("%hi(", color, job, font_id.clone());
|
||||||
write_reloc_name(reloc, color, job);
|
write_reloc_name(reloc, color, job, font_id.clone());
|
||||||
write_text(")", color, job);
|
write_text(")", color, job, font_id.clone());
|
||||||
}
|
}
|
||||||
ObjRelocKind::MipsLo16 => {
|
ObjRelocKind::MipsLo16 => {
|
||||||
write_text("%lo(", color, job);
|
write_text("%lo(", color, job, font_id.clone());
|
||||||
write_reloc_name(reloc, color, job);
|
write_reloc_name(reloc, color, job, font_id.clone());
|
||||||
write_text(")", color, job);
|
write_text(")", color, job, font_id.clone());
|
||||||
}
|
}
|
||||||
ObjRelocKind::Absolute
|
ObjRelocKind::Absolute
|
||||||
| ObjRelocKind::PpcRel24
|
| ObjRelocKind::PpcRel24
|
||||||
| ObjRelocKind::PpcRel14
|
| ObjRelocKind::PpcRel14
|
||||||
| ObjRelocKind::Mips26 => {
|
| ObjRelocKind::Mips26 => {
|
||||||
write_reloc_name(reloc, color, job);
|
write_reloc_name(reloc, color, job, font_id.clone());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -67,6 +67,7 @@ fn write_ins(
|
||||||
args: &[Option<ObjInsArgDiff>],
|
args: &[Option<ObjInsArgDiff>],
|
||||||
base_addr: u32,
|
base_addr: u32,
|
||||||
job: &mut LayoutJob,
|
job: &mut LayoutJob,
|
||||||
|
config: &ViewConfig,
|
||||||
) {
|
) {
|
||||||
let base_color = match diff_kind {
|
let base_color = match diff_kind {
|
||||||
ObjInsDiffKind::None | ObjInsDiffKind::OpMismatch | ObjInsDiffKind::ArgMismatch => {
|
ObjInsDiffKind::None | ObjInsDiffKind::OpMismatch | ObjInsDiffKind::ArgMismatch => {
|
||||||
|
@ -83,54 +84,60 @@ fn write_ins(
|
||||||
_ => base_color,
|
_ => base_color,
|
||||||
},
|
},
|
||||||
job,
|
job,
|
||||||
|
config.code_font.clone(),
|
||||||
);
|
);
|
||||||
let mut writing_offset = false;
|
let mut writing_offset = false;
|
||||||
for (i, arg) in ins.args.iter().enumerate() {
|
for (i, arg) in ins.args.iter().enumerate() {
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
write_text(" ", base_color, job);
|
write_text(" ", base_color, job, config.code_font.clone());
|
||||||
}
|
}
|
||||||
if i > 0 && !writing_offset {
|
if i > 0 && !writing_offset {
|
||||||
write_text(", ", base_color, job);
|
write_text(", ", base_color, job, config.code_font.clone());
|
||||||
}
|
}
|
||||||
let color = if let Some(diff) = args.get(i).and_then(|a| a.as_ref()) {
|
let color = if let Some(diff) = args.get(i).and_then(|a| a.as_ref()) {
|
||||||
COLOR_ROTATION[diff.idx % COLOR_ROTATION.len()]
|
config.diff_colors[diff.idx % config.diff_colors.len()]
|
||||||
} else {
|
} else {
|
||||||
base_color
|
base_color
|
||||||
};
|
};
|
||||||
match arg {
|
match arg {
|
||||||
ObjInsArg::PpcArg(arg) => match arg {
|
ObjInsArg::PpcArg(arg) => match arg {
|
||||||
Argument::Offset(val) => {
|
Argument::Offset(val) => {
|
||||||
write_text(&format!("{}", val), color, job);
|
write_text(&format!("{}", val), color, job, config.code_font.clone());
|
||||||
write_text("(", base_color, job);
|
write_text("(", base_color, job, config.code_font.clone());
|
||||||
writing_offset = true;
|
writing_offset = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Argument::Uimm(_) | Argument::Simm(_) => {
|
Argument::Uimm(_) | Argument::Simm(_) => {
|
||||||
write_text(&format!("{}", arg), color, job);
|
write_text(&format!("{}", arg), color, job, config.code_font.clone());
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
write_text(&format!("{}", arg), color, job);
|
write_text(&format!("{}", arg), color, job, config.code_font.clone());
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ObjInsArg::Reloc => {
|
ObjInsArg::Reloc => {
|
||||||
write_reloc(ins.reloc.as_ref().unwrap(), base_color, job);
|
write_reloc(ins.reloc.as_ref().unwrap(), base_color, job, config.code_font.clone());
|
||||||
}
|
}
|
||||||
ObjInsArg::RelocWithBase => {
|
ObjInsArg::RelocWithBase => {
|
||||||
write_reloc(ins.reloc.as_ref().unwrap(), base_color, job);
|
write_reloc(ins.reloc.as_ref().unwrap(), base_color, job, config.code_font.clone());
|
||||||
write_text("(", base_color, job);
|
write_text("(", base_color, job, config.code_font.clone());
|
||||||
writing_offset = true;
|
writing_offset = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
ObjInsArg::MipsArg(str) => {
|
ObjInsArg::MipsArg(str) => {
|
||||||
write_text(str.strip_prefix('$').unwrap_or(str), color, job);
|
write_text(
|
||||||
|
str.strip_prefix('$').unwrap_or(str),
|
||||||
|
color,
|
||||||
|
job,
|
||||||
|
config.code_font.clone(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
ObjInsArg::BranchOffset(offset) => {
|
ObjInsArg::BranchOffset(offset) => {
|
||||||
let addr = offset + ins.address as i32 - base_addr as i32;
|
let addr = offset + ins.address as i32 - base_addr as i32;
|
||||||
write_text(&format!("{:x}", addr), color, job);
|
write_text(&format!("{:x}", addr), color, job, config.code_font.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if writing_offset {
|
if writing_offset {
|
||||||
write_text(")", base_color, job);
|
write_text(")", base_color, job, config.code_font.clone());
|
||||||
writing_offset = false;
|
writing_offset = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -233,24 +240,12 @@ fn ins_context_menu(ui: &mut egui::Ui, ins: &ObjIns) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const COLOR_ROTATION: [Color32; 9] = [
|
|
||||||
Color32::from_rgb(255, 0, 255),
|
|
||||||
Color32::from_rgb(0, 255, 255),
|
|
||||||
Color32::from_rgb(0, 128, 0),
|
|
||||||
Color32::from_rgb(255, 0, 0),
|
|
||||||
Color32::from_rgb(255, 255, 0),
|
|
||||||
Color32::from_rgb(255, 192, 203),
|
|
||||||
Color32::from_rgb(0, 0, 255),
|
|
||||||
Color32::from_rgb(0, 255, 0),
|
|
||||||
Color32::from_rgb(128, 128, 128),
|
|
||||||
];
|
|
||||||
|
|
||||||
fn find_symbol<'a>(obj: &'a ObjInfo, section_name: &str, name: &str) -> Option<&'a ObjSymbol> {
|
fn find_symbol<'a>(obj: &'a ObjInfo, section_name: &str, name: &str) -> Option<&'a ObjSymbol> {
|
||||||
let section = obj.sections.iter().find(|s| s.name == section_name)?;
|
let section = obj.sections.iter().find(|s| s.name == section_name)?;
|
||||||
section.symbols.iter().find(|s| s.name == name)
|
section.symbols.iter().find(|s| s.name == name)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn asm_row_ui(ui: &mut egui::Ui, ins_diff: &ObjInsDiff, symbol: &ObjSymbol) {
|
fn asm_row_ui(ui: &mut egui::Ui, ins_diff: &ObjInsDiff, symbol: &ObjSymbol, config: &ViewConfig) {
|
||||||
if ins_diff.kind != ObjInsDiffKind::None {
|
if ins_diff.kind != ObjInsDiffKind::None {
|
||||||
ui.painter().rect_filled(ui.available_rect_before_wrap(), 0.0, ui.visuals().faint_bg_color);
|
ui.painter().rect_filled(ui.available_rect_before_wrap(), 0.0, ui.visuals().faint_bg_color);
|
||||||
}
|
}
|
||||||
|
@ -268,15 +263,26 @@ fn asm_row_ui(ui: &mut egui::Ui, ins_diff: &ObjInsDiff, symbol: &ObjSymbol) {
|
||||||
&format!("{:<6}", format!("{:x}:", ins.address - symbol.address as u32)),
|
&format!("{:<6}", format!("{:x}:", ins.address - symbol.address as u32)),
|
||||||
base_color,
|
base_color,
|
||||||
&mut job,
|
&mut job,
|
||||||
|
config.code_font.clone(),
|
||||||
);
|
);
|
||||||
if let Some(branch) = &ins_diff.branch_from {
|
if let Some(branch) = &ins_diff.branch_from {
|
||||||
write_text("~> ", COLOR_ROTATION[branch.branch_idx % COLOR_ROTATION.len()], &mut job);
|
write_text(
|
||||||
|
"~> ",
|
||||||
|
config.diff_colors[branch.branch_idx % config.diff_colors.len()],
|
||||||
|
&mut job,
|
||||||
|
config.code_font.clone(),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
write_text(" ", base_color, &mut job);
|
write_text(" ", base_color, &mut job, config.code_font.clone());
|
||||||
}
|
}
|
||||||
write_ins(ins, &ins_diff.kind, &ins_diff.arg_diff, symbol.address as u32, &mut job);
|
write_ins(ins, &ins_diff.kind, &ins_diff.arg_diff, symbol.address as u32, &mut job, config);
|
||||||
if let Some(branch) = &ins_diff.branch_to {
|
if let Some(branch) = &ins_diff.branch_to {
|
||||||
write_text(" ~>", COLOR_ROTATION[branch.branch_idx % COLOR_ROTATION.len()], &mut job);
|
write_text(
|
||||||
|
" ~>",
|
||||||
|
config.diff_colors[branch.branch_idx % config.diff_colors.len()],
|
||||||
|
&mut job,
|
||||||
|
config.code_font.clone(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
ui.add(Label::new(job).sense(Sense::click()))
|
ui.add(Label::new(job).sense(Sense::click()))
|
||||||
.on_hover_ui_at_pointer(|ui| ins_hover_ui(ui, ins))
|
.on_hover_ui_at_pointer(|ui| ins_hover_ui(ui, ins))
|
||||||
|
@ -291,16 +297,22 @@ fn asm_table_ui(
|
||||||
left_obj: &ObjInfo,
|
left_obj: &ObjInfo,
|
||||||
right_obj: &ObjInfo,
|
right_obj: &ObjInfo,
|
||||||
fn_name: &str,
|
fn_name: &str,
|
||||||
|
config: &ViewConfig,
|
||||||
) -> Option<()> {
|
) -> Option<()> {
|
||||||
let left_symbol = find_symbol(left_obj, ".text", fn_name)?;
|
let left_symbol = find_symbol(left_obj, ".text", fn_name);
|
||||||
let right_symbol = find_symbol(right_obj, ".text", fn_name)?;
|
let right_symbol = find_symbol(right_obj, ".text", fn_name);
|
||||||
|
let instructions_len = left_symbol.or(right_symbol).map(|s| s.instructions.len())?;
|
||||||
table.body(|body| {
|
table.body(|body| {
|
||||||
body.rows(FONT_SIZE, left_symbol.instructions.len(), |row_index, mut row| {
|
body.rows(config.code_font.size, instructions_len, |row_index, mut row| {
|
||||||
row.col(|ui| {
|
row.col(|ui| {
|
||||||
asm_row_ui(ui, &left_symbol.instructions[row_index], left_symbol);
|
if let Some(symbol) = left_symbol {
|
||||||
|
asm_row_ui(ui, &symbol.instructions[row_index], symbol, config);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
row.col(|ui| {
|
row.col(|ui| {
|
||||||
asm_row_ui(ui, &right_symbol.instructions[row_index], right_symbol);
|
if let Some(symbol) = right_symbol {
|
||||||
|
asm_row_ui(ui, &symbol.instructions[row_index], symbol, config);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -379,14 +391,16 @@ pub fn function_diff_ui(ui: &mut egui::Ui, view_state: &mut ViewState) -> bool {
|
||||||
ui.style_mut().override_text_style =
|
ui.style_mut().override_text_style =
|
||||||
Some(egui::TextStyle::Monospace);
|
Some(egui::TextStyle::Monospace);
|
||||||
ui.style_mut().wrap = Some(false);
|
ui.style_mut().wrap = Some(false);
|
||||||
if let Some(obj) = &result.second_obj {
|
if let Some(match_percent) = result
|
||||||
if let Some(symbol) = find_symbol(obj, ".text", selected_symbol)
|
.second_obj
|
||||||
{
|
.as_ref()
|
||||||
ui.colored_label(
|
.and_then(|obj| find_symbol(obj, ".text", selected_symbol))
|
||||||
match_color_for_symbol(symbol),
|
.and_then(|symbol| symbol.match_percent)
|
||||||
&format!("{:.0}%", symbol.match_percent),
|
{
|
||||||
);
|
ui.colored_label(
|
||||||
}
|
match_color_for_symbol(match_percent),
|
||||||
|
&format!("{:.0}%", match_percent),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
ui.label("Diff base:");
|
ui.label("Diff base:");
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
@ -404,7 +418,13 @@ pub fn function_diff_ui(ui: &mut egui::Ui, view_state: &mut ViewState) -> bool {
|
||||||
.column(Size::relative(0.5))
|
.column(Size::relative(0.5))
|
||||||
.column(Size::relative(0.5))
|
.column(Size::relative(0.5))
|
||||||
.resizable(false);
|
.resizable(false);
|
||||||
asm_table_ui(table, left_obj, right_obj, selected_symbol);
|
asm_table_ui(
|
||||||
|
table,
|
||||||
|
left_obj,
|
||||||
|
right_obj,
|
||||||
|
selected_symbol,
|
||||||
|
&view_state.view_config,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,13 +2,26 @@ use egui::{Color32, ProgressBar, Widget};
|
||||||
|
|
||||||
use crate::app::ViewState;
|
use crate::app::ViewState;
|
||||||
|
|
||||||
pub fn jobs_ui(ui: &mut egui::Ui, view_state: &ViewState) {
|
pub fn jobs_ui(ui: &mut egui::Ui, view_state: &mut ViewState) {
|
||||||
ui.label("Jobs");
|
ui.label("Jobs");
|
||||||
|
|
||||||
for job in &view_state.jobs {
|
let mut remove_job: Option<usize> = None;
|
||||||
|
for (idx, job) in view_state.jobs.iter_mut().enumerate() {
|
||||||
if let Ok(status) = job.status.read() {
|
if let Ok(status) = job.status.read() {
|
||||||
ui.group(|ui| {
|
ui.group(|ui| {
|
||||||
ui.label(&status.title);
|
ui.horizontal(|ui| {
|
||||||
|
ui.label(&status.title);
|
||||||
|
if ui.small_button("✖").clicked() {
|
||||||
|
if job.handle.is_some() {
|
||||||
|
job.should_remove = true;
|
||||||
|
if let Err(e) = job.cancel.send(()) {
|
||||||
|
eprintln!("Failed to cancel job: {:?}", e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
remove_job = Some(idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
let mut bar = ProgressBar::new(status.progress_percent);
|
let mut bar = ProgressBar::new(status.progress_percent);
|
||||||
if let Some(items) = &status.progress_items {
|
if let Some(items) = &status.progress_items {
|
||||||
bar = bar.text(format!("{} / {}", items[0], items[1]));
|
bar = bar.text(format!("{} / {}", items[0], items[1]));
|
||||||
|
@ -35,4 +48,8 @@ pub fn jobs_ui(ui: &mut egui::Ui, view_state: &ViewState) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(idx) = remove_job {
|
||||||
|
view_state.jobs.remove(idx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use egui::{text::LayoutJob, Color32, FontFamily, FontId, TextFormat};
|
use egui::{text::LayoutJob, Color32, FontId, TextFormat};
|
||||||
|
|
||||||
pub(crate) mod config;
|
pub(crate) mod config;
|
||||||
pub(crate) mod data_diff;
|
pub(crate) mod data_diff;
|
||||||
|
@ -6,11 +6,8 @@ pub(crate) mod function_diff;
|
||||||
pub(crate) mod jobs;
|
pub(crate) mod jobs;
|
||||||
pub(crate) mod symbol_diff;
|
pub(crate) mod symbol_diff;
|
||||||
|
|
||||||
const FONT_SIZE: f32 = 14.0;
|
|
||||||
const FONT_ID: FontId = FontId::new(FONT_SIZE, FontFamily::Monospace);
|
|
||||||
|
|
||||||
const COLOR_RED: Color32 = Color32::from_rgb(200, 40, 41);
|
const COLOR_RED: Color32 = Color32::from_rgb(200, 40, 41);
|
||||||
|
|
||||||
fn write_text(str: &str, color: Color32, job: &mut LayoutJob) {
|
fn write_text(str: &str, color: Color32, job: &mut LayoutJob, font_id: FontId) {
|
||||||
job.append(str, 0.0, TextFormat { font_id: FONT_ID, color, ..Default::default() });
|
job.append(str, 0.0, TextFormat { font_id, color, ..Default::default() });
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,16 +4,16 @@ use egui::{
|
||||||
use egui_extras::{Size, StripBuilder};
|
use egui_extras::{Size, StripBuilder};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::{View, ViewState},
|
app::{View, ViewConfig, ViewState},
|
||||||
jobs::objdiff::BuildStatus,
|
jobs::objdiff::BuildStatus,
|
||||||
obj::{ObjInfo, ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolFlags},
|
obj::{ObjInfo, ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolFlags},
|
||||||
views::write_text,
|
views::write_text,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn match_color_for_symbol(symbol: &ObjSymbol) -> Color32 {
|
pub fn match_color_for_symbol(match_percent: f32) -> Color32 {
|
||||||
if symbol.match_percent == 100.0 {
|
if match_percent == 100.0 {
|
||||||
Color32::GREEN
|
Color32::GREEN
|
||||||
} else if symbol.match_percent >= 50.0 {
|
} else if match_percent >= 50.0 {
|
||||||
Color32::LIGHT_BLUE
|
Color32::LIGHT_BLUE
|
||||||
} else {
|
} else {
|
||||||
Color32::RED
|
Color32::RED
|
||||||
|
@ -45,7 +45,11 @@ fn symbol_hover_ui(ui: &mut Ui, symbol: &ObjSymbol) {
|
||||||
|
|
||||||
ui.colored_label(Color32::WHITE, format!("Name: {}", symbol.name));
|
ui.colored_label(Color32::WHITE, format!("Name: {}", symbol.name));
|
||||||
ui.colored_label(Color32::WHITE, format!("Address: {:x}", symbol.address));
|
ui.colored_label(Color32::WHITE, format!("Address: {:x}", symbol.address));
|
||||||
ui.colored_label(Color32::WHITE, format!("Size: {:x}", symbol.size));
|
if symbol.size_known {
|
||||||
|
ui.colored_label(Color32::WHITE, format!("Size: {:x}", symbol.size));
|
||||||
|
} else {
|
||||||
|
ui.colored_label(Color32::WHITE, format!("Size: {:x} (assumed)", symbol.size));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,6 +60,7 @@ fn symbol_ui(
|
||||||
highlighted_symbol: &mut Option<String>,
|
highlighted_symbol: &mut Option<String>,
|
||||||
selected_symbol: &mut Option<String>,
|
selected_symbol: &mut Option<String>,
|
||||||
current_view: &mut View,
|
current_view: &mut View,
|
||||||
|
config: &ViewConfig,
|
||||||
) {
|
) {
|
||||||
let mut job = LayoutJob::default();
|
let mut job = LayoutJob::default();
|
||||||
let name: &str =
|
let name: &str =
|
||||||
|
@ -64,28 +69,29 @@ fn symbol_ui(
|
||||||
if let Some(sym) = highlighted_symbol {
|
if let Some(sym) = highlighted_symbol {
|
||||||
selected = sym == &symbol.name;
|
selected = sym == &symbol.name;
|
||||||
}
|
}
|
||||||
write_text("[", Color32::GRAY, &mut job);
|
write_text("[", Color32::GRAY, &mut job, config.code_font.clone());
|
||||||
if symbol.flags.0.contains(ObjSymbolFlags::Common) {
|
if symbol.flags.0.contains(ObjSymbolFlags::Common) {
|
||||||
write_text("c", Color32::from_rgb(0, 255, 255), &mut job);
|
write_text("c", Color32::from_rgb(0, 255, 255), &mut job, config.code_font.clone());
|
||||||
} else if symbol.flags.0.contains(ObjSymbolFlags::Global) {
|
} else if symbol.flags.0.contains(ObjSymbolFlags::Global) {
|
||||||
write_text("g", Color32::GREEN, &mut job);
|
write_text("g", Color32::GREEN, &mut job, config.code_font.clone());
|
||||||
} else if symbol.flags.0.contains(ObjSymbolFlags::Local) {
|
} else if symbol.flags.0.contains(ObjSymbolFlags::Local) {
|
||||||
write_text("l", Color32::GRAY, &mut job);
|
write_text("l", Color32::GRAY, &mut job, config.code_font.clone());
|
||||||
}
|
}
|
||||||
if symbol.flags.0.contains(ObjSymbolFlags::Weak) {
|
if symbol.flags.0.contains(ObjSymbolFlags::Weak) {
|
||||||
write_text("w", Color32::GRAY, &mut job);
|
write_text("w", Color32::GRAY, &mut job, config.code_font.clone());
|
||||||
}
|
}
|
||||||
write_text("] ", Color32::GRAY, &mut job);
|
write_text("] ", Color32::GRAY, &mut job, config.code_font.clone());
|
||||||
if symbol.match_percent > 0.0 {
|
if let Some(match_percent) = symbol.match_percent {
|
||||||
write_text("(", Color32::GRAY, &mut job);
|
write_text("(", Color32::GRAY, &mut job, config.code_font.clone());
|
||||||
write_text(
|
write_text(
|
||||||
&format!("{:.0}%", symbol.match_percent),
|
&format!("{:.0}%", match_percent),
|
||||||
match_color_for_symbol(symbol),
|
match_color_for_symbol(match_percent),
|
||||||
&mut job,
|
&mut job,
|
||||||
|
config.code_font.clone(),
|
||||||
);
|
);
|
||||||
write_text(") ", Color32::GRAY, &mut job);
|
write_text(") ", Color32::GRAY, &mut job, config.code_font.clone());
|
||||||
}
|
}
|
||||||
write_text(name, Color32::WHITE, &mut job);
|
write_text(name, Color32::WHITE, &mut job, config.code_font.clone());
|
||||||
let response = SelectableLabel::new(selected, job)
|
let response = SelectableLabel::new(selected, job)
|
||||||
.ui(ui)
|
.ui(ui)
|
||||||
.context_menu(|ui| symbol_context_menu_ui(ui, symbol))
|
.context_menu(|ui| symbol_context_menu_ui(ui, symbol))
|
||||||
|
@ -123,6 +129,7 @@ fn symbol_list_ui(
|
||||||
current_view: &mut View,
|
current_view: &mut View,
|
||||||
reverse_function_order: bool,
|
reverse_function_order: bool,
|
||||||
search: &mut String,
|
search: &mut String,
|
||||||
|
config: &ViewConfig,
|
||||||
) {
|
) {
|
||||||
ui.text_edit_singleline(search);
|
ui.text_edit_singleline(search);
|
||||||
let lower_search = search.to_ascii_lowercase();
|
let lower_search = search.to_ascii_lowercase();
|
||||||
|
@ -142,6 +149,7 @@ fn symbol_list_ui(
|
||||||
highlighted_symbol,
|
highlighted_symbol,
|
||||||
selected_symbol,
|
selected_symbol,
|
||||||
current_view,
|
current_view,
|
||||||
|
config,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -163,6 +171,7 @@ fn symbol_list_ui(
|
||||||
highlighted_symbol,
|
highlighted_symbol,
|
||||||
selected_symbol,
|
selected_symbol,
|
||||||
current_view,
|
current_view,
|
||||||
|
config,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -177,6 +186,7 @@ fn symbol_list_ui(
|
||||||
highlighted_symbol,
|
highlighted_symbol,
|
||||||
selected_symbol,
|
selected_symbol,
|
||||||
current_view,
|
current_view,
|
||||||
|
config,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -255,6 +265,7 @@ pub fn symbol_diff_ui(ui: &mut Ui, view_state: &mut ViewState) {
|
||||||
current_view,
|
current_view,
|
||||||
view_state.reverse_fn_order,
|
view_state.reverse_fn_order,
|
||||||
search,
|
search,
|
||||||
|
&view_state.view_config,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -274,6 +285,7 @@ pub fn symbol_diff_ui(ui: &mut Ui, view_state: &mut ViewState) {
|
||||||
current_view,
|
current_view,
|
||||||
view_state.reverse_fn_order,
|
view_state.reverse_fn_order,
|
||||||
search,
|
search,
|
||||||
|
&view_state.view_config,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue