mirror of
https://github.com/encounter/objdiff.git
synced 2025-12-17 08:57:25 +00:00
Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0899e6779c | |||
|
|
95528fa8d2 | ||
|
|
517b84e766 | ||
|
|
45dd73f5a9 | ||
|
|
d7d7a7f14a | ||
|
|
441b30070e | ||
|
|
28bd7182d1 | ||
|
|
3f03a75825 | ||
|
|
4fb64a3ad4 | ||
|
|
77c104c67b | ||
|
|
046f3d6999 | ||
|
|
2427b06584 | ||
|
|
157e99de6f | ||
|
|
b571787732 | ||
|
|
dbf86ec3cf | ||
|
|
acc1189150 | ||
|
|
123253c322 | ||
|
|
ef680a5e7d | ||
| 7aa878b48e | |||
| a119d9a6dd | |||
|
|
ebf653816a | ||
| 424434edd6 | |||
| 7f14b684bf | |||
| c5da7f7dd5 | |||
| 2fd655850a | |||
| 79bd7317c1 | |||
| 21f8f2407c | |||
| d2b7a9ef25 | |||
| 2cf9cf24d6 | |||
|
|
5ef3416457 | ||
|
|
6ff8d002f7 | ||
| 9ca157d717 | |||
|
|
67b63311fc | ||
| 72ea1c8911 | |||
| d4a540857d |
5
.cargo/config.toml
Normal file
5
.cargo/config.toml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
[target.x86_64-pc-windows-msvc]
|
||||||
|
linker = "rust-lld"
|
||||||
|
|
||||||
|
[target.aarch64-pc-windows-msvc]
|
||||||
|
linker = "rust-lld"
|
||||||
12
.github/workflows/build.yaml
vendored
12
.github/workflows/build.yaml
vendored
@@ -30,6 +30,8 @@ jobs:
|
|||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
components: clippy
|
components: clippy
|
||||||
|
- name: Cache Rust workspace
|
||||||
|
uses: Swatinem/rust-cache@v2
|
||||||
- name: Cargo check
|
- name: Cargo check
|
||||||
run: cargo check --all-features --all-targets
|
run: cargo check --all-features --all-targets
|
||||||
- name: Cargo clippy
|
- name: Cargo clippy
|
||||||
@@ -85,6 +87,8 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
- name: Setup Rust toolchain
|
- name: Setup Rust toolchain
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
- name: Cache Rust workspace
|
||||||
|
uses: Swatinem/rust-cache@v2
|
||||||
- name: Cargo test
|
- name: Cargo test
|
||||||
run: cargo test --release --all-features
|
run: cargo test --release --all-features
|
||||||
|
|
||||||
@@ -151,6 +155,10 @@ jobs:
|
|||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
targets: ${{ matrix.target }}
|
targets: ${{ matrix.target }}
|
||||||
|
- name: Cache Rust workspace
|
||||||
|
uses: Swatinem/rust-cache@v2
|
||||||
|
with:
|
||||||
|
key: ${{ matrix.target }}
|
||||||
- name: Cargo build
|
- name: Cargo build
|
||||||
run: >
|
run: >
|
||||||
cargo ${{ matrix.build }} --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }}
|
cargo ${{ matrix.build }} --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }}
|
||||||
@@ -202,6 +210,10 @@ jobs:
|
|||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
targets: ${{ matrix.target }}
|
targets: ${{ matrix.target }}
|
||||||
|
- name: Cache Rust workspace
|
||||||
|
uses: Swatinem/rust-cache@v2
|
||||||
|
with:
|
||||||
|
key: ${{ matrix.target }}
|
||||||
- name: Cargo build
|
- name: Cargo build
|
||||||
run: >
|
run: >
|
||||||
cargo build --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }}
|
cargo build --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }}
|
||||||
|
|||||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -3,10 +3,6 @@ target/
|
|||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
generated/
|
generated/
|
||||||
|
|
||||||
# cargo-mobile
|
|
||||||
.cargo/
|
|
||||||
/gen
|
|
||||||
|
|
||||||
# macOS
|
# macOS
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
@@ -22,4 +18,4 @@ android.keystore
|
|||||||
*.frag
|
*.frag
|
||||||
*.vert
|
*.vert
|
||||||
*.metal
|
*.metal
|
||||||
.vscode/launch.json
|
.vscode/
|
||||||
|
|||||||
1167
Cargo.lock
generated
1167
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -13,7 +13,7 @@ strip = "debuginfo"
|
|||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "2.3.2"
|
version = "2.4.0"
|
||||||
authors = ["Luke Street <luke@street.dev>"]
|
authors = ["Luke Street <luke@street.dev>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ Supports:
|
|||||||
- MIPS (N64, PS1, PS2, PSP)
|
- MIPS (N64, PS1, PS2, PSP)
|
||||||
- x86 (COFF only at the moment)
|
- x86 (COFF only at the moment)
|
||||||
- ARM (GBA, DS, 3DS)
|
- ARM (GBA, DS, 3DS)
|
||||||
|
- ARM64 (Switch, experimental)
|
||||||
|
|
||||||
See [Usage](#usage) for more information.
|
See [Usage](#usage) for more information.
|
||||||
|
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ ignore = [
|
|||||||
#{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" },
|
#{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" },
|
||||||
#"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish
|
#"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish
|
||||||
#{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" },
|
#{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" },
|
||||||
|
{ id = "RUSTSEC-2024-0384", reason = "Unmaintained indirect dependency" },
|
||||||
]
|
]
|
||||||
# If this is true, then cargo deny will use the git executable to fetch advisory database.
|
# If this is true, then cargo deny will use the git executable to fetch advisory database.
|
||||||
# If this is false, then it uses a built-in git library.
|
# If this is false, then it uses a built-in git library.
|
||||||
@@ -97,7 +98,7 @@ allow = [
|
|||||||
"BSL-1.0",
|
"BSL-1.0",
|
||||||
"CC0-1.0",
|
"CC0-1.0",
|
||||||
"MPL-2.0",
|
"MPL-2.0",
|
||||||
"Unicode-DFS-2016",
|
"Unicode-3.0",
|
||||||
"Zlib",
|
"Zlib",
|
||||||
"0BSD",
|
"0BSD",
|
||||||
"OFL-1.1",
|
"OFL-1.1",
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ enable-ansi-support = "0.2"
|
|||||||
memmap2 = "0.9"
|
memmap2 = "0.9"
|
||||||
objdiff-core = { path = "../objdiff-core", features = ["all"] }
|
objdiff-core = { path = "../objdiff-core", features = ["all"] }
|
||||||
prost = "0.13"
|
prost = "0.13"
|
||||||
ratatui = "0.28"
|
ratatui = "0.29"
|
||||||
rayon = "1.10"
|
rayon = "1.10"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
|||||||
@@ -844,10 +844,14 @@ impl FunctionDiffUi {
|
|||||||
base_color = COLOR_ROTATION[diff.idx % COLOR_ROTATION.len()]
|
base_color = COLOR_ROTATION[diff.idx % COLOR_ROTATION.len()]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DiffText::Symbol(sym) => {
|
DiffText::Symbol(sym, diff) => {
|
||||||
let name = sym.demangled_name.as_ref().unwrap_or(&sym.name);
|
let name = sym.demangled_name.as_ref().unwrap_or(&sym.name);
|
||||||
label_text = name.clone();
|
label_text = name.clone();
|
||||||
base_color = Color::White;
|
if let Some(diff) = diff {
|
||||||
|
base_color = COLOR_ROTATION[diff.idx % COLOR_ROTATION.len()]
|
||||||
|
} else {
|
||||||
|
base_color = Color::White;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
DiffText::Spacing(n) => {
|
DiffText::Spacing(n) => {
|
||||||
line.spans.push(Span::raw(" ".repeat(n)));
|
line.spans.push(Span::raw(" ".repeat(n)));
|
||||||
|
|||||||
@@ -16,16 +16,17 @@ documentation = "https://docs.rs/objdiff-core"
|
|||||||
crate-type = ["cdylib", "rlib"]
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
all = ["config", "dwarf", "mips", "ppc", "x86", "arm", "bindings"]
|
all = ["config", "dwarf", "mips", "ppc", "x86", "arm", "arm64", "bindings"]
|
||||||
any-arch = ["bimap"] # Implicit, used to check if any arch is enabled
|
any-arch = ["config", "dep:bimap", "dep:strum", "dep:similar", "dep:flagset", "dep:log", "dep:memmap2", "dep:byteorder", "dep:num-traits"] # Implicit, used to check if any arch is enabled
|
||||||
config = ["bimap", "globset", "semver", "serde_json", "serde_yaml"]
|
config = ["dep:bimap", "dep:globset", "dep:semver", "dep:serde_json", "dep:serde_yaml", "dep:serde", "dep:filetime"]
|
||||||
dwarf = ["gimli"]
|
dwarf = ["dep:gimli"]
|
||||||
mips = ["any-arch", "rabbitizer"]
|
mips = ["any-arch", "dep:rabbitizer"]
|
||||||
ppc = ["any-arch", "cwdemangle", "cwextab", "ppc750cl"]
|
ppc = ["any-arch", "dep:cwdemangle", "dep:cwextab", "dep:ppc750cl"]
|
||||||
x86 = ["any-arch", "cpp_demangle", "iced-x86", "msvc-demangler"]
|
x86 = ["any-arch", "dep:cpp_demangle", "dep:iced-x86", "dep:msvc-demangler"]
|
||||||
arm = ["any-arch", "cpp_demangle", "unarm", "arm-attr"]
|
arm = ["any-arch", "dep:cpp_demangle", "dep:unarm", "dep:arm-attr"]
|
||||||
bindings = ["serde_json", "prost", "pbjson"]
|
arm64 = ["any-arch", "dep:cpp_demangle", "dep:yaxpeax-arch", "dep:yaxpeax-arm"]
|
||||||
wasm = ["bindings", "console_error_panic_hook", "console_log"]
|
bindings = ["dep:serde_json", "dep:prost", "dep:pbjson", "dep:serde", "dep:prost-build", "dep:pbjson-build"]
|
||||||
|
wasm = ["bindings", "any-arch", "dep:console_error_panic_hook", "dep:console_log", "dep:wasm-bindgen", "dep:tsify-next", "dep:log"]
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
features = ["all"]
|
features = ["all"]
|
||||||
@@ -33,20 +34,20 @@ features = ["all"]
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
bimap = { version = "0.6", features = ["serde"], optional = true }
|
bimap = { version = "0.6", features = ["serde"], optional = true }
|
||||||
byteorder = "1.5"
|
byteorder = { version = "1.5", optional = true }
|
||||||
filetime = "0.2"
|
filetime = { version = "0.2", optional = true }
|
||||||
flagset = "0.4"
|
flagset = { version = "0.4", optional = true }
|
||||||
log = "0.4"
|
log = { version = "0.4", optional = true }
|
||||||
memmap2 = "0.9"
|
memmap2 = { version = "0.9", optional = true }
|
||||||
num-traits = "0.2"
|
num-traits = { version = "0.2", optional = true }
|
||||||
object = { version = "0.36", features = ["read_core", "std", "elf", "pe"], default-features = false }
|
object = { version = "0.36", features = ["read_core", "std", "elf", "pe"], default-features = false }
|
||||||
pbjson = { version = "0.7", optional = true }
|
pbjson = { version = "0.7", optional = true }
|
||||||
prost = { version = "0.13", optional = true }
|
prost = { version = "0.13", optional = true }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"], optional = true }
|
||||||
similar = { version = "2.6", default-features = false }
|
similar = { version = "2.6", default-features = false, optional = true }
|
||||||
strum = { version = "0.26", features = ["derive"] }
|
strum = { version = "0.26", features = ["derive"], optional = true }
|
||||||
wasm-bindgen = "0.2"
|
wasm-bindgen = { version = "0.2", optional = true }
|
||||||
tsify-next = { version = "0.5", default-features = false, features = ["js"] }
|
tsify-next = { version = "0.5", default-features = false, features = ["js"], optional = true }
|
||||||
console_log = { version = "1.0", optional = true }
|
console_log = { version = "1.0", optional = true }
|
||||||
console_error_panic_hook = { version = "0.1", optional = true }
|
console_error_panic_hook = { version = "0.1", optional = true }
|
||||||
|
|
||||||
@@ -76,6 +77,10 @@ msvc-demangler = { version = "0.10", optional = true }
|
|||||||
unarm = { version = "1.6", optional = true }
|
unarm = { version = "1.6", optional = true }
|
||||||
arm-attr = { version = "0.1", optional = true }
|
arm-attr = { version = "0.1", optional = true }
|
||||||
|
|
||||||
|
# arm64
|
||||||
|
yaxpeax-arch = { version = "0.3", default-features = false, features = ["std"], optional = true }
|
||||||
|
yaxpeax-arm = { version = "0.3", default-features = false, features = ["std"], optional = true }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
prost-build = "0.13"
|
prost-build = { version = "0.13", optional = true }
|
||||||
pbjson-build = "0.7"
|
pbjson-build = { version = "0.7", optional = true }
|
||||||
|
|||||||
@@ -11,4 +11,5 @@ objdiff-core contains the core functionality of [objdiff](https://github.com/enc
|
|||||||
- **`ppc`**: Enables the PowerPC backend powered by [ppc750cl](https://github.com/encounter/ppc750cl).
|
- **`ppc`**: Enables the PowerPC backend powered by [ppc750cl](https://github.com/encounter/ppc750cl).
|
||||||
- **`x86`**: Enables the x86 backend powered by [iced-x86](https://crates.io/crates/iced-x86).
|
- **`x86`**: Enables the x86 backend powered by [iced-x86](https://crates.io/crates/iced-x86).
|
||||||
- **`arm`**: Enables the ARM backend powered by [unarm](https://github.com/AetiasHax/unarm).
|
- **`arm`**: Enables the ARM backend powered by [unarm](https://github.com/AetiasHax/unarm).
|
||||||
|
- **`arm64`**: Enables the ARM64 backend powered by [yaxpeax-arm](https://github.com/iximeow/yaxpeax-arm).
|
||||||
- **`bindings`**: Enables serialization and deserialization of objdiff data structures.
|
- **`bindings`**: Enables serialization and deserialization of objdiff data structures.
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
use std::path::{Path, PathBuf};
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
#[cfg(feature = "bindings")]
|
||||||
|
compile_protos();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "bindings")]
|
||||||
|
fn compile_protos() {
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("protos");
|
let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("protos");
|
||||||
let descriptor_path = root.join("proto_descriptor.bin");
|
let descriptor_path = root.join("proto_descriptor.bin");
|
||||||
println!("cargo:rerun-if-changed={}", descriptor_path.display());
|
println!("cargo:rerun-if-changed={}", descriptor_path.display());
|
||||||
|
|||||||
@@ -124,11 +124,9 @@ impl ObjArch for ObjArchArm {
|
|||||||
.get(&SectionIndex(section_index))
|
.get(&SectionIndex(section_index))
|
||||||
.map(|x| x.as_slice())
|
.map(|x| x.as_slice())
|
||||||
.unwrap_or(&fallback_mappings);
|
.unwrap_or(&fallback_mappings);
|
||||||
let first_mapping_idx =
|
let first_mapping_idx = mapping_symbols
|
||||||
match mapping_symbols.binary_search_by_key(&start_addr, |x| x.address) {
|
.binary_search_by_key(&start_addr, |x| x.address)
|
||||||
Ok(idx) => idx,
|
.unwrap_or_else(|idx| idx - 1);
|
||||||
Err(idx) => idx - 1,
|
|
||||||
};
|
|
||||||
let first_mapping = mapping_symbols[first_mapping_idx].mapping;
|
let first_mapping = mapping_symbols[first_mapping_idx].mapping;
|
||||||
|
|
||||||
let mut mappings_iter =
|
let mut mappings_iter =
|
||||||
@@ -215,7 +213,7 @@ impl ObjArch for ObjArchArm {
|
|||||||
address: address as u64,
|
address: address as u64,
|
||||||
size: (parser.address - address) as u8,
|
size: (parser.address - address) as u8,
|
||||||
op: ins.opcode_id(),
|
op: ins.opcode_id(),
|
||||||
mnemonic: parsed_ins.mnemonic.to_string(),
|
mnemonic: Cow::Borrowed(parsed_ins.mnemonic),
|
||||||
args,
|
args,
|
||||||
reloc,
|
reloc,
|
||||||
branch_dest,
|
branch_dest,
|
||||||
@@ -234,7 +232,7 @@ impl ObjArch for ObjArchArm {
|
|||||||
section: &ObjSection,
|
section: &ObjSection,
|
||||||
address: u64,
|
address: u64,
|
||||||
reloc: &Relocation,
|
reloc: &Relocation,
|
||||||
) -> anyhow::Result<i64> {
|
) -> Result<i64> {
|
||||||
let address = address as usize;
|
let address = address as usize;
|
||||||
Ok(match reloc.flags() {
|
Ok(match reloc.flags() {
|
||||||
// ARM calls
|
// ARM calls
|
||||||
|
|||||||
2840
objdiff-core/src/arch/arm64.rs
Normal file
2840
objdiff-core/src/arch/arm64.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -119,7 +119,7 @@ impl ObjArch for ObjArchMips {
|
|||||||
let op = instruction.unique_id as u16;
|
let op = instruction.unique_id as u16;
|
||||||
ops.push(op);
|
ops.push(op);
|
||||||
|
|
||||||
let mnemonic = instruction.opcode_name().to_string();
|
let mnemonic = instruction.opcode_name();
|
||||||
let is_branch = instruction.is_branch();
|
let is_branch = instruction.is_branch();
|
||||||
let branch_offset = instruction.branch_offset();
|
let branch_offset = instruction.branch_offset();
|
||||||
let mut branch_dest = if is_branch {
|
let mut branch_dest = if is_branch {
|
||||||
@@ -202,7 +202,7 @@ impl ObjArch for ObjArchMips {
|
|||||||
address: cur_addr as u64,
|
address: cur_addr as u64,
|
||||||
size: 4,
|
size: 4,
|
||||||
op,
|
op,
|
||||||
mnemonic,
|
mnemonic: Cow::Borrowed(mnemonic),
|
||||||
args,
|
args,
|
||||||
reloc: reloc.cloned(),
|
reloc: reloc.cloned(),
|
||||||
branch_dest,
|
branch_dest,
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ use crate::{
|
|||||||
|
|
||||||
#[cfg(feature = "arm")]
|
#[cfg(feature = "arm")]
|
||||||
mod arm;
|
mod arm;
|
||||||
|
#[cfg(feature = "arm64")]
|
||||||
|
mod arm64;
|
||||||
#[cfg(feature = "mips")]
|
#[cfg(feature = "mips")]
|
||||||
pub mod mips;
|
pub mod mips;
|
||||||
#[cfg(feature = "ppc")]
|
#[cfg(feature = "ppc")]
|
||||||
@@ -34,7 +36,11 @@ pub enum DataType {
|
|||||||
|
|
||||||
impl DataType {
|
impl DataType {
|
||||||
pub fn display_bytes<Endian: ByteOrder>(&self, bytes: &[u8]) -> Option<String> {
|
pub fn display_bytes<Endian: ByteOrder>(&self, bytes: &[u8]) -> Option<String> {
|
||||||
if self.required_len().is_some_and(|l| bytes.len() < l) {
|
// TODO: Attempt to interpret large symbols as arrays of a smaller type,
|
||||||
|
// fallback to intrepreting it as bytes.
|
||||||
|
// https://github.com/encounter/objdiff/issues/124
|
||||||
|
if self.required_len().is_some_and(|l| bytes.len() != l) {
|
||||||
|
log::warn!("Failed to display a symbol value for a symbol whose size doesn't match the instruction referencing it.");
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,6 +167,8 @@ pub fn new_arch(object: &File) -> Result<Box<dyn ObjArch>> {
|
|||||||
Architecture::I386 | Architecture::X86_64 => Box::new(x86::ObjArchX86::new(object)?),
|
Architecture::I386 | Architecture::X86_64 => Box::new(x86::ObjArchX86::new(object)?),
|
||||||
#[cfg(feature = "arm")]
|
#[cfg(feature = "arm")]
|
||||||
Architecture::Arm => Box::new(arm::ObjArchArm::new(object)?),
|
Architecture::Arm => Box::new(arm::ObjArchArm::new(object)?),
|
||||||
|
#[cfg(feature = "arm64")]
|
||||||
|
Architecture::Aarch64 => Box::new(arm64::ObjArchArm64::new(object)?),
|
||||||
arch => bail!("Unsupported architecture: {arch:?}"),
|
arch => bail!("Unsupported architecture: {arch:?}"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ impl ObjArch for ObjArchPpc {
|
|||||||
insts.push(ObjIns {
|
insts.push(ObjIns {
|
||||||
address: cur_addr as u64,
|
address: cur_addr as u64,
|
||||||
size: 4,
|
size: 4,
|
||||||
mnemonic: simplified.mnemonic.to_string(),
|
mnemonic: Cow::Borrowed(simplified.mnemonic),
|
||||||
args,
|
args,
|
||||||
reloc: reloc.cloned(),
|
reloc: reloc.cloned(),
|
||||||
op: ins.op as u16,
|
op: ins.op as u16,
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ impl ObjArch for ObjArchX86 {
|
|||||||
address: 0,
|
address: 0,
|
||||||
size: 0,
|
size: 0,
|
||||||
op: 0,
|
op: 0,
|
||||||
mnemonic: String::new(),
|
mnemonic: Cow::Borrowed("<invalid>"),
|
||||||
args: vec![],
|
args: vec![],
|
||||||
reloc: None,
|
reloc: None,
|
||||||
branch_dest: None,
|
branch_dest: None,
|
||||||
@@ -76,7 +76,7 @@ impl ObjArch for ObjArchX86 {
|
|||||||
address,
|
address,
|
||||||
size: instruction.len() as u8,
|
size: instruction.len() as u8,
|
||||||
op,
|
op,
|
||||||
mnemonic: String::new(),
|
mnemonic: Cow::Borrowed("<invalid>"),
|
||||||
args: vec![],
|
args: vec![],
|
||||||
reloc: reloc.cloned(),
|
reloc: reloc.cloned(),
|
||||||
branch_dest: None,
|
branch_dest: None,
|
||||||
@@ -242,7 +242,8 @@ impl FormatterOutput for InstructionFormatterOutput {
|
|||||||
|
|
||||||
fn write_mnemonic(&mut self, _instruction: &Instruction, text: &str) {
|
fn write_mnemonic(&mut self, _instruction: &Instruction, text: &str) {
|
||||||
self.formatted.push_str(text);
|
self.formatted.push_str(text);
|
||||||
self.ins.mnemonic = text.to_string();
|
// TODO: can iced-x86 guarantee 'static here?
|
||||||
|
self.ins.mnemonic = Cow::Owned(text.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_number(
|
fn write_number(
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#![allow(clippy::needless_lifetimes)] // Generated serde code
|
||||||
use crate::{
|
use crate::{
|
||||||
diff::{
|
diff::{
|
||||||
ObjDataDiff, ObjDataDiffKind, ObjDiff, ObjInsArgDiff, ObjInsBranchFrom, ObjInsBranchTo,
|
ObjDataDiff, ObjDataDiffKind, ObjDiff, ObjInsArgDiff, ObjInsBranchFrom, ObjInsBranchTo,
|
||||||
@@ -132,7 +133,7 @@ impl Instruction {
|
|||||||
address: instruction.address,
|
address: instruction.address,
|
||||||
size: instruction.size as u32,
|
size: instruction.size as u32,
|
||||||
opcode: instruction.op as u32,
|
opcode: instruction.op as u32,
|
||||||
mnemonic: instruction.mnemonic.clone(),
|
mnemonic: instruction.mnemonic.to_string(),
|
||||||
formatted: instruction.formatted.clone(),
|
formatted: instruction.formatted.clone(),
|
||||||
arguments: instruction.args.iter().map(Argument::new).collect(),
|
arguments: instruction.args.iter().map(Argument::new).collect(),
|
||||||
relocation: instruction.reloc.as_ref().map(|reloc| Relocation::new(object, reloc)),
|
relocation: instruction.reloc.as_ref().map(|reloc| Relocation::new(object, reloc)),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#![allow(clippy::needless_lifetimes)] // Generated serde code
|
||||||
use std::ops::AddAssign;
|
use std::ops::AddAssign;
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
@@ -173,8 +174,7 @@ impl Report {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
fn is_sub_category(id: &str, parent: &str, sep: char) -> bool {
|
fn is_sub_category(id: &str, parent: &str, sep: char) -> bool {
|
||||||
id.starts_with(parent)
|
id.starts_with(parent) && id.get(parent.len()..).is_some_and(|s| s.starts_with(sep))
|
||||||
&& id.get(parent.len()..).map_or(false, |s| s.starts_with(sep))
|
|
||||||
}
|
}
|
||||||
let mut sub_categories = self
|
let mut sub_categories = self
|
||||||
.categories
|
.categories
|
||||||
|
|||||||
@@ -165,6 +165,8 @@ pub struct ScratchConfig {
|
|||||||
pub ctx_path: Option<PathBuf>,
|
pub ctx_path: Option<PathBuf>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub build_ctx: Option<bool>,
|
pub build_ctx: Option<bool>,
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub preset_id: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const CONFIG_FILENAMES: [&str; 3] = ["objdiff.json", "objdiff.yml", "objdiff.yaml"];
|
pub const CONFIG_FILENAMES: [&str; 3] = ["objdiff.json", "objdiff.yml", "objdiff.yaml"];
|
||||||
|
|||||||
@@ -259,11 +259,17 @@ fn arg_eq(
|
|||||||
right_diff.ins.as_ref().and_then(|i| i.reloc.as_ref()),
|
right_diff.ins.as_ref().and_then(|i| i.reloc.as_ref()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
ObjInsArg::BranchDest(_) => {
|
ObjInsArg::BranchDest(_) => match right {
|
||||||
// Compare dest instruction idx after diffing
|
// Compare dest instruction idx after diffing
|
||||||
left_diff.branch_to.as_ref().map(|b| b.ins_idx)
|
ObjInsArg::BranchDest(_) => {
|
||||||
== right_diff.branch_to.as_ref().map(|b| b.ins_idx)
|
left_diff.branch_to.as_ref().map(|b| b.ins_idx)
|
||||||
}
|
== right_diff.branch_to.as_ref().map(|b| b.ins_idx)
|
||||||
|
}
|
||||||
|
// If relocations are relaxed, match if left is a constant and right is a reloc
|
||||||
|
// Useful for instances where the target object is created without relocations
|
||||||
|
ObjInsArg::Reloc => config.relax_reloc_diffs,
|
||||||
|
_ => false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,15 +299,10 @@ fn compare_ins(
|
|||||||
) -> Result<InsDiffResult> {
|
) -> Result<InsDiffResult> {
|
||||||
let mut result = InsDiffResult::default();
|
let mut result = InsDiffResult::default();
|
||||||
if let (Some(left_ins), Some(right_ins)) = (&left.ins, &right.ins) {
|
if let (Some(left_ins), Some(right_ins)) = (&left.ins, &right.ins) {
|
||||||
if left_ins.args.len() != right_ins.args.len()
|
// Count only non-PlainText args
|
||||||
|| left_ins.op != right_ins.op
|
let left_args_count = left_ins.iter_args().count();
|
||||||
// Check if any PlainText segments differ (punctuation and spacing)
|
let right_args_count = right_ins.iter_args().count();
|
||||||
// This indicates a more significant difference than a simple arg mismatch
|
if left_args_count != right_args_count || left_ins.op != right_ins.op {
|
||||||
|| !left_ins.args.iter().zip(&right_ins.args).all(|(a, b)| match (a, b) {
|
|
||||||
(ObjInsArg::PlainText(l), ObjInsArg::PlainText(r)) => l == r,
|
|
||||||
_ => true,
|
|
||||||
})
|
|
||||||
{
|
|
||||||
// Totally different op
|
// Totally different op
|
||||||
result.kind = ObjInsDiffKind::Replace;
|
result.kind = ObjInsDiffKind::Replace;
|
||||||
state.diff_count += 1;
|
state.diff_count += 1;
|
||||||
@@ -312,7 +313,7 @@ fn compare_ins(
|
|||||||
result.kind = ObjInsDiffKind::OpMismatch;
|
result.kind = ObjInsDiffKind::OpMismatch;
|
||||||
state.diff_count += 1;
|
state.diff_count += 1;
|
||||||
}
|
}
|
||||||
for (a, b) in left_ins.args.iter().zip(&right_ins.args) {
|
for (a, b) in left_ins.iter_args().zip(right_ins.iter_args()) {
|
||||||
if arg_eq(config, left_obj, right_obj, a, b, left, right) {
|
if arg_eq(config, left_obj, right_obj, a, b, left, right) {
|
||||||
result.left_args_diff.push(None);
|
result.left_args_diff.push(None);
|
||||||
result.right_args_diff.push(None);
|
result.right_args_diff.push(None);
|
||||||
@@ -324,8 +325,11 @@ fn compare_ins(
|
|||||||
let a_str = match a {
|
let a_str = match a {
|
||||||
ObjInsArg::PlainText(arg) => arg.to_string(),
|
ObjInsArg::PlainText(arg) => arg.to_string(),
|
||||||
ObjInsArg::Arg(arg) => arg.to_string(),
|
ObjInsArg::Arg(arg) => arg.to_string(),
|
||||||
ObjInsArg::Reloc => String::new(),
|
ObjInsArg::Reloc => left_ins
|
||||||
ObjInsArg::BranchDest(arg) => format!("{arg}"),
|
.reloc
|
||||||
|
.as_ref()
|
||||||
|
.map_or_else(|| "<unknown>".to_string(), |r| r.target.name.clone()),
|
||||||
|
ObjInsArg::BranchDest(arg) => arg.to_string(),
|
||||||
};
|
};
|
||||||
let a_diff = if let Some(idx) = state.left_args_idx.get(&a_str) {
|
let a_diff = if let Some(idx) = state.left_args_idx.get(&a_str) {
|
||||||
ObjInsArgDiff { idx: *idx }
|
ObjInsArgDiff { idx: *idx }
|
||||||
@@ -338,8 +342,11 @@ fn compare_ins(
|
|||||||
let b_str = match b {
|
let b_str = match b {
|
||||||
ObjInsArg::PlainText(arg) => arg.to_string(),
|
ObjInsArg::PlainText(arg) => arg.to_string(),
|
||||||
ObjInsArg::Arg(arg) => arg.to_string(),
|
ObjInsArg::Arg(arg) => arg.to_string(),
|
||||||
ObjInsArg::Reloc => String::new(),
|
ObjInsArg::Reloc => right_ins
|
||||||
ObjInsArg::BranchDest(arg) => format!("{arg}"),
|
.reloc
|
||||||
|
.as_ref()
|
||||||
|
.map_or_else(|| "<unknown>".to_string(), |r| r.target.name.clone()),
|
||||||
|
ObjInsArg::BranchDest(arg) => arg.to_string(),
|
||||||
};
|
};
|
||||||
let b_diff = if let Some(idx) = state.right_args_idx.get(&b_str) {
|
let b_diff = if let Some(idx) = state.right_args_idx.get(&b_str) {
|
||||||
ObjInsArgDiff { idx: *idx }
|
ObjInsArgDiff { idx: *idx }
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ pub enum DiffText<'a> {
|
|||||||
/// Branch destination
|
/// Branch destination
|
||||||
BranchDest(u64, Option<&'a ObjInsArgDiff>),
|
BranchDest(u64, Option<&'a ObjInsArgDiff>),
|
||||||
/// Symbol name
|
/// Symbol name
|
||||||
Symbol(&'a ObjSymbol),
|
Symbol(&'a ObjSymbol, Option<&'a ObjInsArgDiff>),
|
||||||
/// Number of spaces
|
/// Number of spaces
|
||||||
Spacing(usize),
|
Spacing(usize),
|
||||||
/// End of line
|
/// End of line
|
||||||
@@ -58,20 +58,23 @@ pub fn display_diff<E>(
|
|||||||
cb(DiffText::Spacing(4))?;
|
cb(DiffText::Spacing(4))?;
|
||||||
}
|
}
|
||||||
cb(DiffText::Opcode(&ins.mnemonic, ins.op))?;
|
cb(DiffText::Opcode(&ins.mnemonic, ins.op))?;
|
||||||
|
let mut arg_diff_idx = 0; // non-PlainText index
|
||||||
for (i, arg) in ins.args.iter().enumerate() {
|
for (i, arg) in ins.args.iter().enumerate() {
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
cb(DiffText::Spacing(1))?;
|
cb(DiffText::Spacing(1))?;
|
||||||
}
|
}
|
||||||
let diff = ins_diff.arg_diff.get(i).and_then(|o| o.as_ref());
|
let diff = ins_diff.arg_diff.get(arg_diff_idx).and_then(|o| o.as_ref());
|
||||||
match arg {
|
match arg {
|
||||||
ObjInsArg::PlainText(s) => {
|
ObjInsArg::PlainText(s) => {
|
||||||
cb(DiffText::Basic(s))?;
|
cb(DiffText::Basic(s))?;
|
||||||
}
|
}
|
||||||
ObjInsArg::Arg(v) => {
|
ObjInsArg::Arg(v) => {
|
||||||
cb(DiffText::Argument(v, diff))?;
|
cb(DiffText::Argument(v, diff))?;
|
||||||
|
arg_diff_idx += 1;
|
||||||
}
|
}
|
||||||
ObjInsArg::Reloc => {
|
ObjInsArg::Reloc => {
|
||||||
display_reloc_name(ins.reloc.as_ref().unwrap(), &mut cb)?;
|
display_reloc_name(ins.reloc.as_ref().unwrap(), &mut cb, diff)?;
|
||||||
|
arg_diff_idx += 1;
|
||||||
}
|
}
|
||||||
ObjInsArg::BranchDest(dest) => {
|
ObjInsArg::BranchDest(dest) => {
|
||||||
if let Some(dest) = dest.checked_sub(base_addr) {
|
if let Some(dest) = dest.checked_sub(base_addr) {
|
||||||
@@ -79,6 +82,7 @@ pub fn display_diff<E>(
|
|||||||
} else {
|
} else {
|
||||||
cb(DiffText::Basic("<unknown>"))?;
|
cb(DiffText::Basic("<unknown>"))?;
|
||||||
}
|
}
|
||||||
|
arg_diff_idx += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -92,8 +96,9 @@ pub fn display_diff<E>(
|
|||||||
fn display_reloc_name<E>(
|
fn display_reloc_name<E>(
|
||||||
reloc: &ObjReloc,
|
reloc: &ObjReloc,
|
||||||
mut cb: impl FnMut(DiffText) -> Result<(), E>,
|
mut cb: impl FnMut(DiffText) -> Result<(), E>,
|
||||||
|
diff: Option<&ObjInsArgDiff>,
|
||||||
) -> Result<(), E> {
|
) -> Result<(), E> {
|
||||||
cb(DiffText::Symbol(&reloc.target))?;
|
cb(DiffText::Symbol(&reloc.target, diff))?;
|
||||||
match reloc.addend.cmp(&0i64) {
|
match reloc.addend.cmp(&0i64) {
|
||||||
Ordering::Greater => cb(DiffText::Basic(&format!("+{:#x}", reloc.addend))),
|
Ordering::Greater => cb(DiffText::Basic(&format!("+{:#x}", reloc.addend))),
|
||||||
Ordering::Less => cb(DiffText::Basic(&format!("-{:#x}", -reloc.addend))),
|
Ordering::Less => cb(DiffText::Basic(&format!("-{:#x}", -reloc.addend))),
|
||||||
@@ -106,7 +111,7 @@ impl PartialEq<DiffText<'_>> for HighlightKind {
|
|||||||
match (self, other) {
|
match (self, other) {
|
||||||
(HighlightKind::Opcode(a), DiffText::Opcode(_, b)) => a == b,
|
(HighlightKind::Opcode(a), DiffText::Opcode(_, b)) => a == b,
|
||||||
(HighlightKind::Arg(a), DiffText::Argument(b, _)) => a.loose_eq(b),
|
(HighlightKind::Arg(a), DiffText::Argument(b, _)) => a.loose_eq(b),
|
||||||
(HighlightKind::Symbol(a), DiffText::Symbol(b)) => a == &b.name,
|
(HighlightKind::Symbol(a), DiffText::Symbol(b, _)) => a == &b.name,
|
||||||
(HighlightKind::Address(a), DiffText::Address(b) | DiffText::BranchDest(b, _)) => {
|
(HighlightKind::Address(a), DiffText::Address(b) | DiffText::BranchDest(b, _)) => {
|
||||||
a == b
|
a == b
|
||||||
}
|
}
|
||||||
@@ -124,7 +129,7 @@ impl From<DiffText<'_>> for HighlightKind {
|
|||||||
match value {
|
match value {
|
||||||
DiffText::Opcode(_, op) => HighlightKind::Opcode(op),
|
DiffText::Opcode(_, op) => HighlightKind::Opcode(op),
|
||||||
DiffText::Argument(arg, _) => HighlightKind::Arg(arg.clone()),
|
DiffText::Argument(arg, _) => HighlightKind::Arg(arg.clone()),
|
||||||
DiffText::Symbol(sym) => HighlightKind::Symbol(sym.name.to_string()),
|
DiffText::Symbol(sym, _) => HighlightKind::Symbol(sym.name.to_string()),
|
||||||
DiffText::Address(addr) | DiffText::BranchDest(addr, _) => HighlightKind::Address(addr),
|
DiffText::Address(addr) | DiffText::BranchDest(addr, _) => HighlightKind::Address(addr),
|
||||||
_ => HighlightKind::None,
|
_ => HighlightKind::None,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use crate::{
|
|||||||
diff_generic_section, no_diff_symbol,
|
diff_generic_section, no_diff_symbol,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
obj::{ObjInfo, ObjIns, ObjSection, ObjSectionKind, ObjSymbol, SymbolRef},
|
obj::{ObjInfo, ObjIns, ObjSection, ObjSectionKind, ObjSymbol, SymbolRef, SECTION_COMMON},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod code;
|
pub mod code;
|
||||||
@@ -29,8 +29,8 @@ pub mod display;
|
|||||||
serde::Serialize,
|
serde::Serialize,
|
||||||
strum::VariantArray,
|
strum::VariantArray,
|
||||||
strum::EnumMessage,
|
strum::EnumMessage,
|
||||||
tsify_next::Tsify,
|
|
||||||
)]
|
)]
|
||||||
|
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify))]
|
||||||
pub enum X86Formatter {
|
pub enum X86Formatter {
|
||||||
#[default]
|
#[default]
|
||||||
#[strum(message = "Intel (default)")]
|
#[strum(message = "Intel (default)")]
|
||||||
@@ -54,8 +54,8 @@ pub enum X86Formatter {
|
|||||||
serde::Serialize,
|
serde::Serialize,
|
||||||
strum::VariantArray,
|
strum::VariantArray,
|
||||||
strum::EnumMessage,
|
strum::EnumMessage,
|
||||||
tsify_next::Tsify,
|
|
||||||
)]
|
)]
|
||||||
|
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify))]
|
||||||
pub enum MipsAbi {
|
pub enum MipsAbi {
|
||||||
#[default]
|
#[default]
|
||||||
#[strum(message = "Auto (default)")]
|
#[strum(message = "Auto (default)")]
|
||||||
@@ -79,8 +79,8 @@ pub enum MipsAbi {
|
|||||||
serde::Serialize,
|
serde::Serialize,
|
||||||
strum::VariantArray,
|
strum::VariantArray,
|
||||||
strum::EnumMessage,
|
strum::EnumMessage,
|
||||||
tsify_next::Tsify,
|
|
||||||
)]
|
)]
|
||||||
|
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify))]
|
||||||
pub enum MipsInstrCategory {
|
pub enum MipsInstrCategory {
|
||||||
#[default]
|
#[default]
|
||||||
#[strum(message = "Auto (default)")]
|
#[strum(message = "Auto (default)")]
|
||||||
@@ -108,8 +108,8 @@ pub enum MipsInstrCategory {
|
|||||||
serde::Serialize,
|
serde::Serialize,
|
||||||
strum::VariantArray,
|
strum::VariantArray,
|
||||||
strum::EnumMessage,
|
strum::EnumMessage,
|
||||||
tsify_next::Tsify,
|
|
||||||
)]
|
)]
|
||||||
|
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify))]
|
||||||
pub enum ArmArchVersion {
|
pub enum ArmArchVersion {
|
||||||
#[default]
|
#[default]
|
||||||
#[strum(message = "Auto (default)")]
|
#[strum(message = "Auto (default)")]
|
||||||
@@ -133,8 +133,8 @@ pub enum ArmArchVersion {
|
|||||||
serde::Serialize,
|
serde::Serialize,
|
||||||
strum::VariantArray,
|
strum::VariantArray,
|
||||||
strum::EnumMessage,
|
strum::EnumMessage,
|
||||||
tsify_next::Tsify,
|
|
||||||
)]
|
)]
|
||||||
|
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify))]
|
||||||
pub enum ArmR9Usage {
|
pub enum ArmR9Usage {
|
||||||
#[default]
|
#[default]
|
||||||
#[strum(
|
#[strum(
|
||||||
@@ -154,8 +154,9 @@ pub enum ArmR9Usage {
|
|||||||
#[inline]
|
#[inline]
|
||||||
const fn default_true() -> bool { true }
|
const fn default_true() -> bool { true }
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize, tsify_next::Tsify)]
|
#[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||||
#[tsify(from_wasm_abi)]
|
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify))]
|
||||||
|
#[cfg_attr(feature = "wasm", tsify(from_wasm_abi))]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct DiffObjConfig {
|
pub struct DiffObjConfig {
|
||||||
pub relax_reloc_diffs: bool,
|
pub relax_reloc_diffs: bool,
|
||||||
@@ -244,7 +245,7 @@ pub struct ObjInsDiff {
|
|||||||
pub branch_from: Option<ObjInsBranchFrom>,
|
pub branch_from: Option<ObjInsBranchFrom>,
|
||||||
/// Branches to instruction
|
/// Branches to instruction
|
||||||
pub branch_to: Option<ObjInsBranchTo>,
|
pub branch_to: Option<ObjInsBranchTo>,
|
||||||
/// Arg diffs
|
/// Arg diffs (only contains non-PlainText args)
|
||||||
pub arg_diff: Vec<Option<ObjInsArgDiff>>,
|
pub arg_diff: Vec<Option<ObjInsArgDiff>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -339,7 +340,7 @@ impl ObjDiff {
|
|||||||
}
|
}
|
||||||
for (symbol_idx, _) in obj.common.iter().enumerate() {
|
for (symbol_idx, _) in obj.common.iter().enumerate() {
|
||||||
result.common.push(ObjSymbolDiff {
|
result.common.push(ObjSymbolDiff {
|
||||||
symbol_ref: SymbolRef { section_idx: obj.sections.len(), symbol_idx },
|
symbol_ref: SymbolRef { section_idx: SECTION_COMMON, symbol_idx },
|
||||||
target_symbol: None,
|
target_symbol: None,
|
||||||
instructions: vec![],
|
instructions: vec![],
|
||||||
match_percent: None,
|
match_percent: None,
|
||||||
@@ -360,7 +361,7 @@ impl ObjDiff {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn symbol_diff(&self, symbol_ref: SymbolRef) -> &ObjSymbolDiff {
|
pub fn symbol_diff(&self, symbol_ref: SymbolRef) -> &ObjSymbolDiff {
|
||||||
if symbol_ref.section_idx == self.sections.len() {
|
if symbol_ref.section_idx == SECTION_COMMON {
|
||||||
&self.common[symbol_ref.symbol_idx]
|
&self.common[symbol_ref.symbol_idx]
|
||||||
} else {
|
} else {
|
||||||
&self.section_diff(symbol_ref.section_idx).symbols[symbol_ref.symbol_idx]
|
&self.section_diff(symbol_ref.section_idx).symbols[symbol_ref.symbol_idx]
|
||||||
@@ -369,7 +370,7 @@ impl ObjDiff {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn symbol_diff_mut(&mut self, symbol_ref: SymbolRef) -> &mut ObjSymbolDiff {
|
pub fn symbol_diff_mut(&mut self, symbol_ref: SymbolRef) -> &mut ObjSymbolDiff {
|
||||||
if symbol_ref.section_idx == self.sections.len() {
|
if symbol_ref.section_idx == SECTION_COMMON {
|
||||||
&mut self.common[symbol_ref.symbol_idx]
|
&mut self.common[symbol_ref.symbol_idx]
|
||||||
} else {
|
} else {
|
||||||
&mut self.section_diff_mut(symbol_ref.section_idx).symbols[symbol_ref.symbol_idx]
|
&mut self.section_diff_mut(symbol_ref.section_idx).symbols[symbol_ref.symbol_idx]
|
||||||
@@ -576,7 +577,7 @@ fn generate_mapping_symbols(
|
|||||||
let Some(base_symbol_ref) = symbol_ref_by_name(base_obj, base_name) else {
|
let Some(base_symbol_ref) = symbol_ref_by_name(base_obj, base_name) else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
let (base_section, base_symbol) = base_obj.section_symbol(base_symbol_ref);
|
let (base_section, _base_symbol) = base_obj.section_symbol(base_symbol_ref);
|
||||||
let Some(base_section) = base_section else {
|
let Some(base_section) = base_section else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
@@ -587,9 +588,7 @@ fn generate_mapping_symbols(
|
|||||||
for (target_section_index, target_section) in
|
for (target_section_index, target_section) in
|
||||||
target_obj.sections.iter().enumerate().filter(|(_, s)| s.kind == base_section.kind)
|
target_obj.sections.iter().enumerate().filter(|(_, s)| s.kind == base_section.kind)
|
||||||
{
|
{
|
||||||
for (target_symbol_index, _target_symbol) in
|
for (target_symbol_index, _target_symbol) in target_section.symbols.iter().enumerate() {
|
||||||
target_section.symbols.iter().enumerate().filter(|(_, s)| s.kind == base_symbol.kind)
|
|
||||||
{
|
|
||||||
let target_symbol_ref =
|
let target_symbol_ref =
|
||||||
SymbolRef { section_idx: target_section_index, symbol_idx: target_symbol_index };
|
SymbolRef { section_idx: target_section_index, symbol_idx: target_symbol_index };
|
||||||
match base_section.kind {
|
match base_section.kind {
|
||||||
@@ -757,7 +756,7 @@ fn matching_symbols(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (symbol_idx, symbol) in left.common.iter().enumerate() {
|
for (symbol_idx, symbol) in left.common.iter().enumerate() {
|
||||||
let symbol_ref = SymbolRef { section_idx: left.sections.len(), symbol_idx };
|
let symbol_ref = SymbolRef { section_idx: SECTION_COMMON, symbol_idx };
|
||||||
if left_used.contains(&symbol_ref) {
|
if left_used.contains(&symbol_ref) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -789,7 +788,7 @@ fn matching_symbols(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (symbol_idx, symbol) in right.common.iter().enumerate() {
|
for (symbol_idx, symbol) in right.common.iter().enumerate() {
|
||||||
let symbol_ref = SymbolRef { section_idx: right.sections.len(), symbol_idx };
|
let symbol_ref = SymbolRef { section_idx: SECTION_COMMON, symbol_idx };
|
||||||
if right_used.contains(&symbol_ref) {
|
if right_used.contains(&symbol_ref) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -882,7 +881,7 @@ fn find_common_symbol(obj: Option<&ObjInfo>, in_symbol: &ObjSymbol) -> Option<Sy
|
|||||||
let obj = obj?;
|
let obj = obj?;
|
||||||
for (symbol_idx, symbol) in obj.common.iter().enumerate() {
|
for (symbol_idx, symbol) in obj.common.iter().enumerate() {
|
||||||
if symbol.name == in_symbol.name {
|
if symbol.name == in_symbol.name {
|
||||||
return Some(SymbolRef { section_idx: obj.sections.len(), symbol_idx });
|
return Some(SymbolRef { section_idx: SECTION_COMMON, symbol_idx });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
|
|||||||
@@ -8,4 +8,5 @@ pub mod config;
|
|||||||
pub mod diff;
|
pub mod diff;
|
||||||
#[cfg(feature = "any-arch")]
|
#[cfg(feature = "any-arch")]
|
||||||
pub mod obj;
|
pub mod obj;
|
||||||
|
#[cfg(feature = "any-arch")]
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
|||||||
@@ -85,6 +85,9 @@ pub enum ObjInsArg {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ObjInsArg {
|
impl ObjInsArg {
|
||||||
|
#[inline]
|
||||||
|
pub fn is_plain_text(&self) -> bool { matches!(self, ObjInsArg::PlainText(_)) }
|
||||||
|
|
||||||
pub fn loose_eq(&self, other: &ObjInsArg) -> bool {
|
pub fn loose_eq(&self, other: &ObjInsArg) -> bool {
|
||||||
match (self, other) {
|
match (self, other) {
|
||||||
(ObjInsArg::Arg(a), ObjInsArg::Arg(b)) => a.loose_eq(b),
|
(ObjInsArg::Arg(a), ObjInsArg::Arg(b)) => a.loose_eq(b),
|
||||||
@@ -100,7 +103,7 @@ pub struct ObjIns {
|
|||||||
pub address: u64,
|
pub address: u64,
|
||||||
pub size: u8,
|
pub size: u8,
|
||||||
pub op: u16,
|
pub op: u16,
|
||||||
pub mnemonic: String,
|
pub mnemonic: Cow<'static, str>,
|
||||||
pub args: Vec<ObjInsArg>,
|
pub args: Vec<ObjInsArg>,
|
||||||
pub reloc: Option<ObjReloc>,
|
pub reloc: Option<ObjReloc>,
|
||||||
pub branch_dest: Option<u64>,
|
pub branch_dest: Option<u64>,
|
||||||
@@ -112,6 +115,14 @@ pub struct ObjIns {
|
|||||||
pub orig: Option<String>,
|
pub orig: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ObjIns {
|
||||||
|
/// Iterate over non-PlainText arguments.
|
||||||
|
#[inline]
|
||||||
|
pub fn iter_args(&self) -> impl DoubleEndedIterator<Item = &ObjInsArg> {
|
||||||
|
self.args.iter().filter(|a| !a.is_plain_text())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
|
||||||
pub enum ObjSymbolKind {
|
pub enum ObjSymbolKind {
|
||||||
#[default]
|
#[default]
|
||||||
@@ -164,9 +175,11 @@ pub struct SymbolRef {
|
|||||||
pub symbol_idx: usize,
|
pub symbol_idx: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const SECTION_COMMON: usize = usize::MAX - 1;
|
||||||
|
|
||||||
impl ObjInfo {
|
impl ObjInfo {
|
||||||
pub fn section_symbol(&self, symbol_ref: SymbolRef) -> (Option<&ObjSection>, &ObjSymbol) {
|
pub fn section_symbol(&self, symbol_ref: SymbolRef) -> (Option<&ObjSection>, &ObjSymbol) {
|
||||||
if symbol_ref.section_idx == self.sections.len() {
|
if symbol_ref.section_idx == SECTION_COMMON {
|
||||||
let symbol = &self.common[symbol_ref.symbol_idx];
|
let symbol = &self.common[symbol_ref.symbol_idx];
|
||||||
return (None, symbol);
|
return (None, symbol);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,10 +64,8 @@ fn to_obj_symbol(
|
|||||||
if obj_file.format() == BinaryFormat::Elf && symbol.scope() == SymbolScope::Linkage {
|
if obj_file.format() == BinaryFormat::Elf && symbol.scope() == SymbolScope::Linkage {
|
||||||
flags = ObjSymbolFlagSet(flags.0 | ObjSymbolFlags::Hidden);
|
flags = ObjSymbolFlagSet(flags.0 | ObjSymbolFlags::Hidden);
|
||||||
}
|
}
|
||||||
if arch
|
#[cfg(feature = "ppc")]
|
||||||
.ppc()
|
if arch.ppc().and_then(|a| a.extab.as_ref()).is_some_and(|e| e.contains_key(&symbol.index().0))
|
||||||
.and_then(|a| a.extab.as_ref())
|
|
||||||
.map_or(false, |e| e.contains_key(&symbol.index().0))
|
|
||||||
{
|
{
|
||||||
flags = ObjSymbolFlagSet(flags.0 | ObjSymbolFlags::HasExtra);
|
flags = ObjSymbolFlagSet(flags.0 | ObjSymbolFlags::HasExtra);
|
||||||
}
|
}
|
||||||
@@ -334,6 +332,10 @@ fn relocations_by_section(
|
|||||||
};
|
};
|
||||||
symbol
|
symbol
|
||||||
}
|
}
|
||||||
|
RelocationTarget::Absolute => {
|
||||||
|
log::warn!("Ignoring absolute relocation @ {}:{:#x}", section.name, address);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
_ => bail!("Unhandled relocation target: {:?}", reloc.target()),
|
_ => bail!("Unhandled relocation target: {:?}", reloc.target()),
|
||||||
};
|
};
|
||||||
let flags = reloc.flags(); // TODO validate reloc here?
|
let flags = reloc.flags(); // TODO validate reloc here?
|
||||||
@@ -431,9 +433,9 @@ fn line_info(obj_file: &File<'_>, sections: &mut [ObjSection], obj_data: &[u8])
|
|||||||
let mut text_sections =
|
let mut text_sections =
|
||||||
obj_file.sections().filter(|s| s.kind() == SectionKind::Text);
|
obj_file.sections().filter(|s| s.kind() == SectionKind::Text);
|
||||||
let section_index = text_sections.next().map(|s| s.index().0);
|
let section_index = text_sections.next().map(|s| s.index().0);
|
||||||
let mut lines = section_index.map(|index| {
|
let mut lines = section_index
|
||||||
&mut sections.iter_mut().find(|s| s.orig_index == index).unwrap().line_info
|
.and_then(|index| sections.iter_mut().find(|s| s.orig_index == index))
|
||||||
});
|
.map(|s| &mut s.line_info);
|
||||||
|
|
||||||
let mut rows = program.rows();
|
let mut rows = program.rows();
|
||||||
while let Some((_header, row)) = rows.next_row()? {
|
while let Some((_header, row)) = rows.next_row()? {
|
||||||
@@ -444,13 +446,9 @@ fn line_info(obj_file: &File<'_>, sections: &mut [ObjSection], obj_data: &[u8])
|
|||||||
// The next row is the start of a new sequence, which means we must
|
// The next row is the start of a new sequence, which means we must
|
||||||
// advance to the next .text section.
|
// advance to the next .text section.
|
||||||
let section_index = text_sections.next().map(|s| s.index().0);
|
let section_index = text_sections.next().map(|s| s.index().0);
|
||||||
lines = section_index.map(|index| {
|
lines = section_index
|
||||||
&mut sections
|
.and_then(|index| sections.iter_mut().find(|s| s.orig_index == index))
|
||||||
.iter_mut()
|
.map(|s| &mut s.line_info);
|
||||||
.find(|s| s.orig_index == index)
|
|
||||||
.unwrap()
|
|
||||||
.line_info
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ wsl = []
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
bytes = "1.7"
|
bytes = "1.9"
|
||||||
cfg-if = "1.0"
|
cfg-if = "1.0"
|
||||||
const_format = "0.2"
|
const_format = "0.2"
|
||||||
cwdemangle = "1.0"
|
cwdemangle = "1.0"
|
||||||
@@ -42,7 +42,7 @@ notify = { git = "https://github.com/notify-rs/notify", rev = "128bf6230c03d39db
|
|||||||
objdiff-core = { path = "../objdiff-core", features = ["all"] }
|
objdiff-core = { path = "../objdiff-core", features = ["all"] }
|
||||||
open = "5.3"
|
open = "5.3"
|
||||||
png = "0.17"
|
png = "0.17"
|
||||||
pollster = "0.3"
|
pollster = "0.4"
|
||||||
regex = "1.11"
|
regex = "1.11"
|
||||||
rfd = { version = "0.15" } #, default-features = false, features = ['xdg-portal']
|
rfd = { version = "0.15" } #, default-features = false, features = ['xdg-portal']
|
||||||
rlwinmdec = "1.0"
|
rlwinmdec = "1.0"
|
||||||
@@ -51,7 +51,7 @@ serde = { version = "1.0", features = ["derive"] }
|
|||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
shell-escape = "0.1"
|
shell-escape = "0.1"
|
||||||
strum = { version = "0.26", features = ["derive"] }
|
strum = { version = "0.26", features = ["derive"] }
|
||||||
tempfile = "3.13"
|
tempfile = "3.14"
|
||||||
time = { version = "0.3", features = ["formatting", "local-offset"] }
|
time = { version = "0.3", features = ["formatting", "local-offset"] }
|
||||||
|
|
||||||
# Keep version in sync with egui
|
# Keep version in sync with egui
|
||||||
@@ -95,7 +95,7 @@ exec = "0.3"
|
|||||||
|
|
||||||
# native:
|
# native:
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
tracing-subscriber = "0.3"
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
|
||||||
# web:
|
# web:
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ use time::UtcOffset;
|
|||||||
use crate::{
|
use crate::{
|
||||||
app_config::{deserialize_config, AppConfigVersion},
|
app_config::{deserialize_config, AppConfigVersion},
|
||||||
config::{load_project_config, ProjectObjectNode},
|
config::{load_project_config, ProjectObjectNode},
|
||||||
|
hotkeys,
|
||||||
jobs::{
|
jobs::{
|
||||||
objdiff::{start_build, ObjDiffConfig},
|
objdiff::{start_build, ObjDiffConfig},
|
||||||
Job, JobQueue, JobResult, JobStatus,
|
Job, JobQueue, JobResult, JobStatus,
|
||||||
@@ -527,6 +528,10 @@ impl App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn post_update(&mut self, ctx: &egui::Context, action: Option<DiffViewAction>) {
|
fn post_update(&mut self, ctx: &egui::Context, action: Option<DiffViewAction>) {
|
||||||
|
if action.is_some() {
|
||||||
|
ctx.request_repaint();
|
||||||
|
}
|
||||||
|
|
||||||
self.appearance.post_update(ctx);
|
self.appearance.post_update(ctx);
|
||||||
|
|
||||||
let ViewState { jobs, diff_state, config_state, graphics_state, .. } = &mut self.view_state;
|
let ViewState { jobs, diff_state, config_state, graphics_state, .. } = &mut self.view_state;
|
||||||
@@ -690,13 +695,13 @@ impl eframe::App for App {
|
|||||||
*show_side_panel = !*show_side_panel;
|
*show_side_panel = !*show_side_panel;
|
||||||
}
|
}
|
||||||
ui.separator();
|
ui.separator();
|
||||||
ui.menu_button("File", |ui| {
|
ui.menu_button(hotkeys::button_alt_text(ui, "_File"), |ui| {
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
if ui.button("Debug…").clicked() {
|
if ui.button(hotkeys::button_alt_text(ui, "_Debug…")).clicked() {
|
||||||
*show_debug = !*show_debug;
|
*show_debug = !*show_debug;
|
||||||
ui.close_menu();
|
ui.close_menu();
|
||||||
}
|
}
|
||||||
if ui.button("Project…").clicked() {
|
if ui.button(hotkeys::button_alt_text(ui, "_Project…")).clicked() {
|
||||||
*show_project_config = !*show_project_config;
|
*show_project_config = !*show_project_config;
|
||||||
ui.close_menu();
|
ui.close_menu();
|
||||||
}
|
}
|
||||||
@@ -705,10 +710,11 @@ impl eframe::App for App {
|
|||||||
} else {
|
} else {
|
||||||
vec![]
|
vec![]
|
||||||
};
|
};
|
||||||
|
let recent_projects_text = hotkeys::button_alt_text(ui, "_Recent Projects…");
|
||||||
if recent_projects.is_empty() {
|
if recent_projects.is_empty() {
|
||||||
ui.add_enabled(false, egui::Button::new("Recent projects…"));
|
ui.add_enabled(false, egui::Button::new(recent_projects_text));
|
||||||
} else {
|
} else {
|
||||||
ui.menu_button("Recent Projects…", |ui| {
|
ui.menu_button(recent_projects_text, |ui| {
|
||||||
if ui.button("Clear").clicked() {
|
if ui.button("Clear").clicked() {
|
||||||
state.write().unwrap().config.recent_projects.clear();
|
state.write().unwrap().config.recent_projects.clear();
|
||||||
};
|
};
|
||||||
@@ -721,36 +727,39 @@ impl eframe::App for App {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if ui.button("Appearance…").clicked() {
|
if ui.button(hotkeys::button_alt_text(ui, "_Appearance…")).clicked() {
|
||||||
*show_appearance_config = !*show_appearance_config;
|
*show_appearance_config = !*show_appearance_config;
|
||||||
ui.close_menu();
|
ui.close_menu();
|
||||||
}
|
}
|
||||||
if ui.button("Graphics…").clicked() {
|
if ui.button(hotkeys::button_alt_text(ui, "_Graphics…")).clicked() {
|
||||||
*show_graphics = !*show_graphics;
|
*show_graphics = !*show_graphics;
|
||||||
ui.close_menu();
|
ui.close_menu();
|
||||||
}
|
}
|
||||||
if ui.button("Quit").clicked() {
|
if ui.button(hotkeys::button_alt_text(ui, "_Quit")).clicked() {
|
||||||
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
|
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
ui.menu_button("Tools", |ui| {
|
ui.menu_button(hotkeys::button_alt_text(ui, "_Tools"), |ui| {
|
||||||
if ui.button("Demangle…").clicked() {
|
if ui.button(hotkeys::button_alt_text(ui, "_Demangle…")).clicked() {
|
||||||
*show_demangle = !*show_demangle;
|
*show_demangle = !*show_demangle;
|
||||||
ui.close_menu();
|
ui.close_menu();
|
||||||
}
|
}
|
||||||
if ui.button("Rlwinm Decoder…").clicked() {
|
if ui.button(hotkeys::button_alt_text(ui, "_Rlwinm Decoder…")).clicked() {
|
||||||
*show_rlwinm_decode = !*show_rlwinm_decode;
|
*show_rlwinm_decode = !*show_rlwinm_decode;
|
||||||
ui.close_menu();
|
ui.close_menu();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
ui.menu_button("Diff Options", |ui| {
|
ui.menu_button(hotkeys::button_alt_text(ui, "_Diff Options"), |ui| {
|
||||||
if ui.button("Arch Settings…").clicked() {
|
if ui.button(hotkeys::button_alt_text(ui, "_Arch Settings…")).clicked() {
|
||||||
*show_arch_config = !*show_arch_config;
|
*show_arch_config = !*show_arch_config;
|
||||||
ui.close_menu();
|
ui.close_menu();
|
||||||
}
|
}
|
||||||
let mut state = state.write().unwrap();
|
let mut state = state.write().unwrap();
|
||||||
let response = ui
|
let response = ui
|
||||||
.checkbox(&mut state.config.rebuild_on_changes, "Rebuild on changes")
|
.checkbox(
|
||||||
|
&mut state.config.rebuild_on_changes,
|
||||||
|
hotkeys::button_alt_text(ui, "_Rebuild on changes"),
|
||||||
|
)
|
||||||
.on_hover_text("Automatically re-run the build & diff when files change.");
|
.on_hover_text("Automatically re-run the build & diff when files change.");
|
||||||
if response.changed() {
|
if response.changed() {
|
||||||
state.watcher_change = true;
|
state.watcher_change = true;
|
||||||
@@ -759,18 +768,21 @@ impl eframe::App for App {
|
|||||||
!diff_state.symbol_state.disable_reverse_fn_order,
|
!diff_state.symbol_state.disable_reverse_fn_order,
|
||||||
egui::Checkbox::new(
|
egui::Checkbox::new(
|
||||||
&mut diff_state.symbol_state.reverse_fn_order,
|
&mut diff_state.symbol_state.reverse_fn_order,
|
||||||
"Reverse function order (-inline deferred)",
|
hotkeys::button_alt_text(
|
||||||
|
ui,
|
||||||
|
"Reverse function order (-inline _deferred)",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.on_disabled_hover_text(CONFIG_DISABLED_TEXT);
|
.on_disabled_hover_text(CONFIG_DISABLED_TEXT);
|
||||||
ui.checkbox(
|
ui.checkbox(
|
||||||
&mut diff_state.symbol_state.show_hidden_symbols,
|
&mut diff_state.symbol_state.show_hidden_symbols,
|
||||||
"Show hidden symbols",
|
hotkeys::button_alt_text(ui, "Show _hidden symbols"),
|
||||||
);
|
);
|
||||||
if ui
|
if ui
|
||||||
.checkbox(
|
.checkbox(
|
||||||
&mut state.config.diff_obj_config.relax_reloc_diffs,
|
&mut state.config.diff_obj_config.relax_reloc_diffs,
|
||||||
"Relax relocation diffs",
|
hotkeys::button_alt_text(ui, "Rela_x relocation diffs"),
|
||||||
)
|
)
|
||||||
.on_hover_text(
|
.on_hover_text(
|
||||||
"Ignores differences in relocation targets. (Address, name, etc)",
|
"Ignores differences in relocation targets. (Address, name, etc)",
|
||||||
@@ -782,7 +794,7 @@ impl eframe::App for App {
|
|||||||
if ui
|
if ui
|
||||||
.checkbox(
|
.checkbox(
|
||||||
&mut state.config.diff_obj_config.space_between_args,
|
&mut state.config.diff_obj_config.space_between_args,
|
||||||
"Space between args",
|
hotkeys::button_alt_text(ui, "_Space between args"),
|
||||||
)
|
)
|
||||||
.changed()
|
.changed()
|
||||||
{
|
{
|
||||||
@@ -791,14 +803,17 @@ impl eframe::App for App {
|
|||||||
if ui
|
if ui
|
||||||
.checkbox(
|
.checkbox(
|
||||||
&mut state.config.diff_obj_config.combine_data_sections,
|
&mut state.config.diff_obj_config.combine_data_sections,
|
||||||
"Combine data sections",
|
hotkeys::button_alt_text(ui, "Combine _data sections"),
|
||||||
)
|
)
|
||||||
.on_hover_text("Combines data sections with equal names.")
|
.on_hover_text("Combines data sections with equal names.")
|
||||||
.changed()
|
.changed()
|
||||||
{
|
{
|
||||||
state.queue_reload = true;
|
state.queue_reload = true;
|
||||||
}
|
}
|
||||||
if ui.button("Clear custom symbol mappings").clicked() {
|
if ui
|
||||||
|
.button(hotkeys::button_alt_text(ui, "_Clear custom symbol mappings"))
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
state.clear_mappings();
|
state.clear_mappings();
|
||||||
diff_state.post_build_nav = Some(DiffViewNavigation::symbol_diff());
|
diff_state.post_build_nav = Some(DiffViewNavigation::symbol_diff());
|
||||||
state.queue_reload = true;
|
state.queue_reload = true;
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ impl ScratchConfigV1 {
|
|||||||
c_flags: self.c_flags,
|
c_flags: self.c_flags,
|
||||||
ctx_path: self.ctx_path,
|
ctx_path: self.ctx_path,
|
||||||
build_ctx: self.build_ctx.then_some(true),
|
build_ctx: self.build_ctx.then_some(true),
|
||||||
|
preset_id: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,21 @@ pub enum ProjectObjectNode {
|
|||||||
Dir(String, Vec<ProjectObjectNode>),
|
Dir(String, Vec<ProjectObjectNode>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn join_single_dir_entries(nodes: &mut Vec<ProjectObjectNode>) {
|
||||||
|
for node in nodes {
|
||||||
|
if let ProjectObjectNode::Dir(my_name, my_nodes) = node {
|
||||||
|
join_single_dir_entries(my_nodes);
|
||||||
|
// If this directory consists of a single sub-directory...
|
||||||
|
if let [ProjectObjectNode::Dir(sub_name, sub_nodes)] = &mut my_nodes[..] {
|
||||||
|
// ... join the two names with a path separator and eliminate the layer
|
||||||
|
*my_name += "/";
|
||||||
|
*my_name += sub_name;
|
||||||
|
*my_nodes = std::mem::take(sub_nodes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn find_dir<'a>(
|
fn find_dir<'a>(
|
||||||
name: &str,
|
name: &str,
|
||||||
nodes: &'a mut Vec<ProjectObjectNode>,
|
nodes: &'a mut Vec<ProjectObjectNode>,
|
||||||
@@ -60,6 +75,14 @@ fn build_nodes(
|
|||||||
let filename = path.file_name().unwrap().to_str().unwrap().to_string();
|
let filename = path.file_name().unwrap().to_str().unwrap().to_string();
|
||||||
out_nodes.push(ProjectObjectNode::Unit(filename, idx));
|
out_nodes.push(ProjectObjectNode::Unit(filename, idx));
|
||||||
}
|
}
|
||||||
|
// Within the top-level module directories, join paths. Leave the
|
||||||
|
// top-level name intact though since it's the module name.
|
||||||
|
for node in &mut nodes {
|
||||||
|
if let ProjectObjectNode::Dir(_, sub_nodes) = node {
|
||||||
|
join_single_dir_entries(sub_nodes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
nodes
|
nodes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
228
objdiff-gui/src/hotkeys.rs
Normal file
228
objdiff-gui/src/hotkeys.rs
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
use egui::{
|
||||||
|
style::ScrollAnimation, text::LayoutJob, vec2, Align, Context, FontSelection, Key,
|
||||||
|
KeyboardShortcut, Modifiers, PointerButton, RichText, Ui, WidgetText,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn any_widget_focused(ctx: &Context) -> bool { ctx.memory(|mem| mem.focused().is_some()) }
|
||||||
|
|
||||||
|
pub fn enter_pressed(ctx: &Context) -> bool {
|
||||||
|
if any_widget_focused(ctx) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ctx.input_mut(|i| {
|
||||||
|
i.key_pressed(Key::Enter)
|
||||||
|
|| i.key_pressed(Key::Space)
|
||||||
|
|| i.pointer.button_pressed(PointerButton::Extra2)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn back_pressed(ctx: &Context) -> bool {
|
||||||
|
if any_widget_focused(ctx) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ctx.input_mut(|i| {
|
||||||
|
i.key_pressed(Key::Backspace)
|
||||||
|
|| i.key_pressed(Key::Escape)
|
||||||
|
|| i.pointer.button_pressed(PointerButton::Extra1)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn up_pressed(ctx: &Context) -> bool {
|
||||||
|
if any_widget_focused(ctx) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ctx.input_mut(|i| i.key_pressed(Key::ArrowUp) || i.key_pressed(Key::W))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn down_pressed(ctx: &Context) -> bool {
|
||||||
|
if any_widget_focused(ctx) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ctx.input_mut(|i| i.key_pressed(Key::ArrowDown) || i.key_pressed(Key::S))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn page_up_pressed(ctx: &Context) -> bool { ctx.input_mut(|i| i.key_pressed(Key::PageUp)) }
|
||||||
|
|
||||||
|
pub fn page_down_pressed(ctx: &Context) -> bool { ctx.input_mut(|i| i.key_pressed(Key::PageDown)) }
|
||||||
|
|
||||||
|
pub fn home_pressed(ctx: &Context) -> bool { ctx.input_mut(|i| i.key_pressed(Key::Home)) }
|
||||||
|
|
||||||
|
pub fn end_pressed(ctx: &Context) -> bool { ctx.input_mut(|i| i.key_pressed(Key::End)) }
|
||||||
|
|
||||||
|
pub fn check_scroll_hotkeys(ui: &mut egui::Ui, include_small_increments: bool) {
|
||||||
|
let ui_height = ui.available_rect_before_wrap().height();
|
||||||
|
if up_pressed(ui.ctx()) && include_small_increments {
|
||||||
|
ui.scroll_with_delta_animation(vec2(0.0, ui_height / 10.0), ScrollAnimation::none());
|
||||||
|
} else if down_pressed(ui.ctx()) && include_small_increments {
|
||||||
|
ui.scroll_with_delta_animation(vec2(0.0, -ui_height / 10.0), ScrollAnimation::none());
|
||||||
|
} else if page_up_pressed(ui.ctx()) {
|
||||||
|
ui.scroll_with_delta_animation(vec2(0.0, ui_height), ScrollAnimation::none());
|
||||||
|
} else if page_down_pressed(ui.ctx()) {
|
||||||
|
ui.scroll_with_delta_animation(vec2(0.0, -ui_height), ScrollAnimation::none());
|
||||||
|
} else if home_pressed(ui.ctx()) {
|
||||||
|
ui.scroll_with_delta_animation(vec2(0.0, f32::INFINITY), ScrollAnimation::none());
|
||||||
|
} else if end_pressed(ui.ctx()) {
|
||||||
|
ui.scroll_with_delta_animation(vec2(0.0, -f32::INFINITY), ScrollAnimation::none());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn consume_up_key(ctx: &Context) -> bool {
|
||||||
|
if any_widget_focused(ctx) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ctx.input_mut(|i| {
|
||||||
|
i.consume_key(Modifiers::NONE, Key::ArrowUp) || i.consume_key(Modifiers::NONE, Key::W)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn consume_down_key(ctx: &Context) -> bool {
|
||||||
|
if any_widget_focused(ctx) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ctx.input_mut(|i| {
|
||||||
|
i.consume_key(Modifiers::NONE, Key::ArrowDown) || i.consume_key(Modifiers::NONE, Key::S)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const OBJECT_FILTER_SHORTCUT: KeyboardShortcut = KeyboardShortcut::new(Modifiers::CTRL, Key::F);
|
||||||
|
|
||||||
|
pub fn consume_object_filter_shortcut(ctx: &Context) -> bool {
|
||||||
|
ctx.input_mut(|i| i.consume_shortcut(&OBJECT_FILTER_SHORTCUT))
|
||||||
|
}
|
||||||
|
|
||||||
|
const SYMBOL_FILTER_SHORTCUT: KeyboardShortcut = KeyboardShortcut::new(Modifiers::CTRL, Key::S);
|
||||||
|
|
||||||
|
pub fn consume_symbol_filter_shortcut(ctx: &Context) -> bool {
|
||||||
|
ctx.input_mut(|i| i.consume_shortcut(&SYMBOL_FILTER_SHORTCUT))
|
||||||
|
}
|
||||||
|
|
||||||
|
const CHANGE_TARGET_SHORTCUT: KeyboardShortcut = KeyboardShortcut::new(Modifiers::CTRL, Key::T);
|
||||||
|
|
||||||
|
pub fn consume_change_target_shortcut(ctx: &Context) -> bool {
|
||||||
|
ctx.input_mut(|i| i.consume_shortcut(&CHANGE_TARGET_SHORTCUT))
|
||||||
|
}
|
||||||
|
|
||||||
|
const CHANGE_BASE_SHORTCUT: KeyboardShortcut = KeyboardShortcut::new(Modifiers::CTRL, Key::B);
|
||||||
|
|
||||||
|
pub fn consume_change_base_shortcut(ctx: &Context) -> bool {
|
||||||
|
ctx.input_mut(|i| i.consume_shortcut(&CHANGE_BASE_SHORTCUT))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn shortcut_key(text: &str) -> (usize, char, Key) {
|
||||||
|
let i = text.chars().position(|c| c == '_').expect("No underscore in text");
|
||||||
|
let key = text.chars().nth(i + 1).expect("No character after underscore");
|
||||||
|
let shortcut_key = match key {
|
||||||
|
'a' | 'A' => Key::A,
|
||||||
|
'b' | 'B' => Key::B,
|
||||||
|
'c' | 'C' => Key::C,
|
||||||
|
'd' | 'D' => Key::D,
|
||||||
|
'e' | 'E' => Key::E,
|
||||||
|
'f' | 'F' => Key::F,
|
||||||
|
'g' | 'G' => Key::G,
|
||||||
|
'h' | 'H' => Key::H,
|
||||||
|
'i' | 'I' => Key::I,
|
||||||
|
'j' | 'J' => Key::J,
|
||||||
|
'k' | 'K' => Key::K,
|
||||||
|
'l' | 'L' => Key::L,
|
||||||
|
'm' | 'M' => Key::M,
|
||||||
|
'n' | 'N' => Key::N,
|
||||||
|
'o' | 'O' => Key::O,
|
||||||
|
'p' | 'P' => Key::P,
|
||||||
|
'q' | 'Q' => Key::Q,
|
||||||
|
'r' | 'R' => Key::R,
|
||||||
|
's' | 'S' => Key::S,
|
||||||
|
't' | 'T' => Key::T,
|
||||||
|
'u' | 'U' => Key::U,
|
||||||
|
'v' | 'V' => Key::V,
|
||||||
|
'w' | 'W' => Key::W,
|
||||||
|
'x' | 'X' => Key::X,
|
||||||
|
'y' | 'Y' => Key::Y,
|
||||||
|
'z' | 'Z' => Key::Z,
|
||||||
|
_ => panic!("Invalid key {}", key),
|
||||||
|
};
|
||||||
|
(i, key, shortcut_key)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn alt_text_ui(ui: &Ui, text: &str, i: usize, key: char, interactive: bool) -> WidgetText {
|
||||||
|
let color = if interactive {
|
||||||
|
ui.visuals().widgets.inactive.text_color()
|
||||||
|
} else {
|
||||||
|
ui.visuals().widgets.noninteractive.text_color()
|
||||||
|
};
|
||||||
|
let mut job = LayoutJob::default();
|
||||||
|
if i > 0 {
|
||||||
|
let text = &text[..i];
|
||||||
|
RichText::new(text).color(color).append_to(
|
||||||
|
&mut job,
|
||||||
|
ui.style(),
|
||||||
|
FontSelection::Default,
|
||||||
|
Align::Center,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let mut rt = RichText::new(key).color(color);
|
||||||
|
if ui.input(|i| i.modifiers.alt) {
|
||||||
|
rt = rt.underline();
|
||||||
|
}
|
||||||
|
rt.append_to(&mut job, ui.style(), FontSelection::Default, Align::Center);
|
||||||
|
if i < text.len() - 1 {
|
||||||
|
let text = &text[i + 2..];
|
||||||
|
RichText::new(text).color(color).append_to(
|
||||||
|
&mut job,
|
||||||
|
ui.style(),
|
||||||
|
FontSelection::Default,
|
||||||
|
Align::Center,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
job.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn button_alt_text(ui: &Ui, text: &str) -> WidgetText {
|
||||||
|
let (n, c, key) = shortcut_key(text);
|
||||||
|
let result = alt_text_ui(ui, text, n, c, true);
|
||||||
|
if ui.input_mut(|i| check_alt_key(i, c, key)) {
|
||||||
|
let btn_id = ui.next_auto_id();
|
||||||
|
ui.memory_mut(|m| m.request_focus(btn_id));
|
||||||
|
ui.input_mut(|i| {
|
||||||
|
i.events.push(egui::Event::Key {
|
||||||
|
key: Key::Enter,
|
||||||
|
physical_key: None,
|
||||||
|
pressed: true,
|
||||||
|
repeat: false,
|
||||||
|
modifiers: Default::default(),
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn alt_text(ui: &Ui, text: &str, enter: bool) -> WidgetText {
|
||||||
|
let (n, c, key) = shortcut_key(text);
|
||||||
|
let result = alt_text_ui(ui, text, n, c, false);
|
||||||
|
if ui.input_mut(|i| check_alt_key(i, c, key)) {
|
||||||
|
let btn_id = ui.next_auto_id();
|
||||||
|
ui.memory_mut(|m| m.request_focus(btn_id));
|
||||||
|
if enter {
|
||||||
|
ui.input_mut(|i| {
|
||||||
|
i.events.push(egui::Event::Key {
|
||||||
|
key: Key::Enter,
|
||||||
|
physical_key: None,
|
||||||
|
pressed: true,
|
||||||
|
repeat: false,
|
||||||
|
modifiers: Default::default(),
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_alt_key(i: &mut egui::InputState, c: char, key: Key) -> bool {
|
||||||
|
if i.consume_key(Modifiers::ALT, key) {
|
||||||
|
// Remove any text input events that match the key
|
||||||
|
let cs = c.to_string();
|
||||||
|
i.events.retain(|e| !matches!(e, egui::Event::Text(t) if *t == cs));
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,6 +23,7 @@ pub struct CreateScratchConfig {
|
|||||||
pub compiler_flags: String,
|
pub compiler_flags: String,
|
||||||
pub function_name: String,
|
pub function_name: String,
|
||||||
pub target_obj: PathBuf,
|
pub target_obj: PathBuf,
|
||||||
|
pub preset_id: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CreateScratchConfig {
|
impl CreateScratchConfig {
|
||||||
@@ -45,6 +46,7 @@ impl CreateScratchConfig {
|
|||||||
compiler_flags: scratch_config.c_flags.clone().unwrap_or_default(),
|
compiler_flags: scratch_config.c_flags.clone().unwrap_or_default(),
|
||||||
function_name,
|
function_name,
|
||||||
target_obj: target_path.to_path_buf(),
|
target_obj: target_path.to_path_buf(),
|
||||||
|
preset_id: scratch_config.preset_id,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,15 +103,18 @@ fn run_create_scratch(
|
|||||||
let obj_path = project_dir.join(&config.target_obj);
|
let obj_path = project_dir.join(&config.target_obj);
|
||||||
let file = reqwest::blocking::multipart::Part::file(&obj_path)
|
let file = reqwest::blocking::multipart::Part::file(&obj_path)
|
||||||
.with_context(|| format!("Failed to open {}", obj_path.display()))?;
|
.with_context(|| format!("Failed to open {}", obj_path.display()))?;
|
||||||
let form = reqwest::blocking::multipart::Form::new()
|
let mut form = reqwest::blocking::multipart::Form::new()
|
||||||
.text("compiler", config.compiler.clone())
|
.text("compiler", config.compiler.clone())
|
||||||
.text("platform", config.platform.clone())
|
.text("platform", config.platform.clone())
|
||||||
.text("compiler_flags", config.compiler_flags.clone())
|
.text("compiler_flags", config.compiler_flags.clone())
|
||||||
.text("diff_label", config.function_name.clone())
|
.text("diff_label", config.function_name.clone())
|
||||||
.text("diff_flags", diff_flags)
|
.text("diff_flags", diff_flags)
|
||||||
.text("context", context.unwrap_or_default())
|
.text("context", context.unwrap_or_default())
|
||||||
.text("source_code", "// Move related code from Context tab to here")
|
.text("source_code", "// Move related code from Context tab to here");
|
||||||
.part("target_obj", file);
|
if let Some(preset) = config.preset_id {
|
||||||
|
form = form.text("preset", preset.to_string());
|
||||||
|
}
|
||||||
|
form = form.part("target_obj", file);
|
||||||
let client = reqwest::blocking::Client::new();
|
let client = reqwest::blocking::Client::new();
|
||||||
let response = client
|
let response = client
|
||||||
.post(formatcp!("{API_HOST}/api/scratch"))
|
.post(formatcp!("{API_HOST}/api/scratch"))
|
||||||
|
|||||||
@@ -152,16 +152,14 @@ fn start_job(
|
|||||||
let context = JobContext { status: status.clone(), egui: ctx.clone() };
|
let context = JobContext { status: status.clone(), egui: ctx.clone() };
|
||||||
let context_inner = JobContext { status: status.clone(), egui: ctx.clone() };
|
let context_inner = JobContext { status: status.clone(), egui: ctx.clone() };
|
||||||
let (tx, rx) = std::sync::mpsc::channel();
|
let (tx, rx) = std::sync::mpsc::channel();
|
||||||
let handle = std::thread::spawn(move || {
|
let handle = std::thread::spawn(move || match run(context_inner, rx) {
|
||||||
return match run(context_inner, rx) {
|
Ok(state) => state,
|
||||||
Ok(state) => state,
|
Err(e) => {
|
||||||
Err(e) => {
|
if let Ok(mut w) = status.write() {
|
||||||
if let Ok(mut w) = status.write() {
|
w.error = Some(e);
|
||||||
w.error = Some(e);
|
|
||||||
}
|
|
||||||
JobResult::None
|
|
||||||
}
|
}
|
||||||
};
|
JobResult::None
|
||||||
|
}
|
||||||
});
|
});
|
||||||
let id = JOB_ID.fetch_add(1, Ordering::Relaxed);
|
let id = JOB_ID.fetch_add(1, Ordering::Relaxed);
|
||||||
log::info!("Started job {}", id);
|
log::info!("Started job {}", id);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ mod app;
|
|||||||
mod app_config;
|
mod app_config;
|
||||||
mod config;
|
mod config;
|
||||||
mod fonts;
|
mod fonts;
|
||||||
|
mod hotkeys;
|
||||||
mod jobs;
|
mod jobs;
|
||||||
mod update;
|
mod update;
|
||||||
mod views;
|
mod views;
|
||||||
|
|||||||
@@ -205,7 +205,7 @@ pub const DEFAULT_COLOR_ROTATION: [Color32; 9] = [
|
|||||||
Color32::from_rgb(255, 0, 0),
|
Color32::from_rgb(255, 0, 0),
|
||||||
Color32::from_rgb(255, 255, 0),
|
Color32::from_rgb(255, 255, 0),
|
||||||
Color32::from_rgb(255, 192, 203),
|
Color32::from_rgb(255, 192, 203),
|
||||||
Color32::from_rgb(0, 0, 255),
|
Color32::from_rgb(128, 128, 255),
|
||||||
Color32::from_rgb(0, 255, 0),
|
Color32::from_rgb(0, 255, 0),
|
||||||
Color32::from_rgb(213, 138, 138),
|
Color32::from_rgb(213, 138, 138),
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ use strum::{EnumMessage, VariantArray};
|
|||||||
use crate::{
|
use crate::{
|
||||||
app::{AppConfig, AppState, AppStateRef, ObjectConfig},
|
app::{AppConfig, AppState, AppStateRef, ObjectConfig},
|
||||||
config::ProjectObjectNode,
|
config::ProjectObjectNode,
|
||||||
|
hotkeys,
|
||||||
jobs::{
|
jobs::{
|
||||||
check_update::{start_check_update, CheckUpdateResult},
|
check_update::{start_check_update, CheckUpdateResult},
|
||||||
update::start_update,
|
update::start_update,
|
||||||
@@ -218,7 +219,7 @@ pub fn config_ui(
|
|||||||
|
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.heading("Project");
|
ui.heading("Project");
|
||||||
if ui.button(RichText::new("Settings")).clicked() {
|
if ui.button("Settings").clicked() {
|
||||||
*show_config_window = true;
|
*show_config_window = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -254,7 +255,12 @@ pub fn config_ui(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let had_search = !config_state.object_search.is_empty();
|
let had_search = !config_state.object_search.is_empty();
|
||||||
egui::TextEdit::singleline(&mut config_state.object_search).hint_text("Filter").ui(ui);
|
let response = egui::TextEdit::singleline(&mut config_state.object_search)
|
||||||
|
.hint_text(hotkeys::alt_text(ui, "Filter _objects", false))
|
||||||
|
.ui(ui);
|
||||||
|
if hotkeys::consume_object_filter_shortcut(ui.ctx()) {
|
||||||
|
response.request_focus();
|
||||||
|
}
|
||||||
|
|
||||||
let mut root_open = None;
|
let mut root_open = None;
|
||||||
let mut node_open = NodeOpen::Default;
|
let mut node_open = NodeOpen::Default;
|
||||||
|
|||||||
@@ -7,11 +7,14 @@ use objdiff_core::{
|
|||||||
};
|
};
|
||||||
use time::format_description;
|
use time::format_description;
|
||||||
|
|
||||||
use crate::views::{
|
use crate::{
|
||||||
appearance::Appearance,
|
hotkeys,
|
||||||
column_layout::{render_header, render_table},
|
views::{
|
||||||
symbol_diff::{DiffViewAction, DiffViewNavigation, DiffViewState},
|
appearance::Appearance,
|
||||||
write_text,
|
column_layout::{render_header, render_table},
|
||||||
|
symbol_diff::{DiffViewAction, DiffViewNavigation, DiffViewState},
|
||||||
|
write_text,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const BYTES_PER_ROW: usize = 16;
|
const BYTES_PER_ROW: usize = 16;
|
||||||
@@ -176,6 +179,8 @@ fn data_table_ui(
|
|||||||
let left_diffs = left_section.map(|(_, section)| split_diffs(§ion.data_diff));
|
let left_diffs = left_section.map(|(_, section)| split_diffs(§ion.data_diff));
|
||||||
let right_diffs = right_section.map(|(_, section)| split_diffs(§ion.data_diff));
|
let right_diffs = right_section.map(|(_, section)| split_diffs(§ion.data_diff));
|
||||||
|
|
||||||
|
hotkeys::check_scroll_hotkeys(ui, true);
|
||||||
|
|
||||||
render_table(ui, available_width, 2, config.code_font.size, total_rows, |row, column| {
|
render_table(ui, available_width, 2, config.code_font.size, total_rows, |row, column| {
|
||||||
let i = row.index();
|
let i = row.index();
|
||||||
let address = i * BYTES_PER_ROW;
|
let address = i * BYTES_PER_ROW;
|
||||||
@@ -213,8 +218,8 @@ pub fn data_diff_ui(
|
|||||||
let right_ctx = SectionDiffContext::new(result.second_obj.as_ref(), section_name);
|
let right_ctx = SectionDiffContext::new(result.second_obj.as_ref(), section_name);
|
||||||
|
|
||||||
// If both sides are missing a symbol, switch to symbol diff view
|
// If both sides are missing a symbol, switch to symbol diff view
|
||||||
if !right_ctx.map_or(false, |ctx| ctx.has_section())
|
if !right_ctx.is_some_and(|ctx| ctx.has_section())
|
||||||
&& !left_ctx.map_or(false, |ctx| ctx.has_section())
|
&& !left_ctx.is_some_and(|ctx| ctx.has_section())
|
||||||
{
|
{
|
||||||
return Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff()));
|
return Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff()));
|
||||||
}
|
}
|
||||||
@@ -224,7 +229,7 @@ pub fn data_diff_ui(
|
|||||||
render_header(ui, available_width, 2, |ui, column| {
|
render_header(ui, available_width, 2, |ui, column| {
|
||||||
if column == 0 {
|
if column == 0 {
|
||||||
// Left column
|
// Left column
|
||||||
if ui.button("⏴ Back").clicked() {
|
if ui.button("⏴ Back").clicked() || hotkeys::back_pressed(ui.ctx()) {
|
||||||
ret = Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff()));
|
ret = Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,13 +5,16 @@ use objdiff_core::{
|
|||||||
};
|
};
|
||||||
use time::format_description;
|
use time::format_description;
|
||||||
|
|
||||||
use crate::views::{
|
use crate::{
|
||||||
appearance::Appearance,
|
hotkeys,
|
||||||
column_layout::{render_header, render_strips},
|
views::{
|
||||||
function_diff::FunctionDiffContext,
|
appearance::Appearance,
|
||||||
symbol_diff::{
|
column_layout::{render_header, render_strips},
|
||||||
match_color_for_symbol, DiffViewAction, DiffViewNavigation, DiffViewState, SymbolRefByName,
|
function_diff::FunctionDiffContext,
|
||||||
View,
|
symbol_diff::{
|
||||||
|
match_color_for_symbol, DiffViewAction, DiffViewNavigation, DiffViewState,
|
||||||
|
SymbolRefByName, View,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -101,7 +104,7 @@ pub fn extab_diff_ui(
|
|||||||
let right_diff_symbol = right_ctx.and_then(|ctx| {
|
let right_diff_symbol = right_ctx.and_then(|ctx| {
|
||||||
ctx.symbol_ref.and_then(|symbol_ref| ctx.diff.symbol_diff(symbol_ref).target_symbol)
|
ctx.symbol_ref.and_then(|symbol_ref| ctx.diff.symbol_diff(symbol_ref).target_symbol)
|
||||||
});
|
});
|
||||||
if left_diff_symbol.is_some() && right_ctx.map_or(false, |ctx| !ctx.has_symbol()) {
|
if left_diff_symbol.is_some() && right_ctx.is_some_and(|ctx| !ctx.has_symbol()) {
|
||||||
let (right_section, right_symbol) =
|
let (right_section, right_symbol) =
|
||||||
right_ctx.unwrap().obj.section_symbol(left_diff_symbol.unwrap());
|
right_ctx.unwrap().obj.section_symbol(left_diff_symbol.unwrap());
|
||||||
let symbol_ref = SymbolRefByName::new(right_symbol, right_section);
|
let symbol_ref = SymbolRefByName::new(right_symbol, right_section);
|
||||||
@@ -111,7 +114,7 @@ pub fn extab_diff_ui(
|
|||||||
left_symbol: state.symbol_state.left_symbol.clone(),
|
left_symbol: state.symbol_state.left_symbol.clone(),
|
||||||
right_symbol: Some(symbol_ref),
|
right_symbol: Some(symbol_ref),
|
||||||
}));
|
}));
|
||||||
} else if right_diff_symbol.is_some() && left_ctx.map_or(false, |ctx| !ctx.has_symbol()) {
|
} else if right_diff_symbol.is_some() && left_ctx.is_some_and(|ctx| !ctx.has_symbol()) {
|
||||||
let (left_section, left_symbol) =
|
let (left_section, left_symbol) =
|
||||||
left_ctx.unwrap().obj.section_symbol(right_diff_symbol.unwrap());
|
left_ctx.unwrap().obj.section_symbol(right_diff_symbol.unwrap());
|
||||||
let symbol_ref = SymbolRefByName::new(left_symbol, left_section);
|
let symbol_ref = SymbolRefByName::new(left_symbol, left_section);
|
||||||
@@ -124,8 +127,8 @@ pub fn extab_diff_ui(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If both sides are missing a symbol, switch to symbol diff view
|
// If both sides are missing a symbol, switch to symbol diff view
|
||||||
if right_ctx.map_or(false, |ctx| !ctx.has_symbol())
|
if right_ctx.is_some_and(|ctx| !ctx.has_symbol())
|
||||||
&& left_ctx.map_or(false, |ctx| !ctx.has_symbol())
|
&& left_ctx.is_some_and(|ctx| !ctx.has_symbol())
|
||||||
{
|
{
|
||||||
return Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff()));
|
return Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff()));
|
||||||
}
|
}
|
||||||
@@ -136,7 +139,7 @@ pub fn extab_diff_ui(
|
|||||||
if column == 0 {
|
if column == 0 {
|
||||||
// Left column
|
// Left column
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
if ui.button("⏴ Back").clicked() {
|
if ui.button("⏴ Back").clicked() || hotkeys::back_pressed(ui.ctx()) {
|
||||||
ret = Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff()));
|
ret = Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff()));
|
||||||
}
|
}
|
||||||
ui.separator();
|
ui.separator();
|
||||||
@@ -144,7 +147,7 @@ pub fn extab_diff_ui(
|
|||||||
.add_enabled(
|
.add_enabled(
|
||||||
!state.scratch_running
|
!state.scratch_running
|
||||||
&& state.scratch_available
|
&& state.scratch_available
|
||||||
&& left_ctx.map_or(false, |ctx| ctx.has_symbol()),
|
&& left_ctx.is_some_and(|ctx| ctx.has_symbol()),
|
||||||
egui::Button::new("📲 decomp.me"),
|
egui::Button::new("📲 decomp.me"),
|
||||||
)
|
)
|
||||||
.on_hover_text_at_pointer("Create a new scratch on decomp.me (beta)")
|
.on_hover_text_at_pointer("Create a new scratch on decomp.me (beta)")
|
||||||
@@ -232,6 +235,8 @@ pub fn extab_diff_ui(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
hotkeys::check_scroll_hotkeys(ui, true);
|
||||||
|
|
||||||
// Table
|
// Table
|
||||||
render_strips(ui, available_width, 2, |ui, column| {
|
render_strips(ui, available_width, 2, |ui, column| {
|
||||||
if column == 0 {
|
if column == 0 {
|
||||||
|
|||||||
@@ -14,12 +14,15 @@ use objdiff_core::{
|
|||||||
};
|
};
|
||||||
use time::format_description;
|
use time::format_description;
|
||||||
|
|
||||||
use crate::views::{
|
use crate::{
|
||||||
appearance::Appearance,
|
hotkeys,
|
||||||
column_layout::{render_header, render_strips, render_table},
|
views::{
|
||||||
symbol_diff::{
|
appearance::Appearance,
|
||||||
match_color_for_symbol, symbol_list_ui, DiffViewAction, DiffViewNavigation, DiffViewState,
|
column_layout::{render_header, render_strips, render_table},
|
||||||
SymbolDiffContext, SymbolFilter, SymbolRefByName, SymbolViewState, View,
|
symbol_diff::{
|
||||||
|
match_color_for_symbol, symbol_list_ui, DiffViewAction, DiffViewNavigation,
|
||||||
|
DiffViewState, SymbolDiffContext, SymbolFilter, SymbolRefByName, SymbolViewState, View,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -292,10 +295,14 @@ fn diff_text_ui(
|
|||||||
base_color = appearance.diff_colors[diff.idx % appearance.diff_colors.len()]
|
base_color = appearance.diff_colors[diff.idx % appearance.diff_colors.len()]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DiffText::Symbol(sym) => {
|
DiffText::Symbol(sym, diff) => {
|
||||||
let name = sym.demangled_name.as_ref().unwrap_or(&sym.name);
|
let name = sym.demangled_name.as_ref().unwrap_or(&sym.name);
|
||||||
label_text = name.clone();
|
label_text = name.clone();
|
||||||
base_color = appearance.emphasized_text_color;
|
if let Some(diff) = diff {
|
||||||
|
base_color = appearance.diff_colors[diff.idx % appearance.diff_colors.len()]
|
||||||
|
} else {
|
||||||
|
base_color = appearance.emphasized_text_color;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
DiffText::Spacing(n) => {
|
DiffText::Spacing(n) => {
|
||||||
ui.add_space(n as f32 * space_width);
|
ui.add_space(n as f32 * space_width);
|
||||||
@@ -428,6 +435,7 @@ fn asm_table_ui(
|
|||||||
};
|
};
|
||||||
if left_len.is_some() && right_len.is_some() {
|
if left_len.is_some() && right_len.is_some() {
|
||||||
// Joint view
|
// Joint view
|
||||||
|
hotkeys::check_scroll_hotkeys(ui, true);
|
||||||
render_table(
|
render_table(
|
||||||
ui,
|
ui,
|
||||||
available_width,
|
available_width,
|
||||||
@@ -463,6 +471,7 @@ fn asm_table_ui(
|
|||||||
if column == 0 {
|
if column == 0 {
|
||||||
if let Some(ctx) = left_ctx {
|
if let Some(ctx) = left_ctx {
|
||||||
if ctx.has_symbol() {
|
if ctx.has_symbol() {
|
||||||
|
hotkeys::check_scroll_hotkeys(ui, false);
|
||||||
render_table(
|
render_table(
|
||||||
ui,
|
ui,
|
||||||
available_width / 2.0,
|
available_width / 2.0,
|
||||||
@@ -508,9 +517,6 @@ fn asm_table_ui(
|
|||||||
SymbolRefByName::new(right_symbol, right_section),
|
SymbolRefByName::new(right_symbol, right_section),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
DiffViewAction::SetSymbolHighlight(_, _) => {
|
|
||||||
// Ignore
|
|
||||||
}
|
|
||||||
_ => {
|
_ => {
|
||||||
ret = Some(action);
|
ret = Some(action);
|
||||||
}
|
}
|
||||||
@@ -523,6 +529,7 @@ fn asm_table_ui(
|
|||||||
} else if column == 1 {
|
} else if column == 1 {
|
||||||
if let Some(ctx) = right_ctx {
|
if let Some(ctx) = right_ctx {
|
||||||
if ctx.has_symbol() {
|
if ctx.has_symbol() {
|
||||||
|
hotkeys::check_scroll_hotkeys(ui, false);
|
||||||
render_table(
|
render_table(
|
||||||
ui,
|
ui,
|
||||||
available_width / 2.0,
|
available_width / 2.0,
|
||||||
@@ -568,9 +575,6 @@ fn asm_table_ui(
|
|||||||
right_symbol_ref,
|
right_symbol_ref,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
DiffViewAction::SetSymbolHighlight(_, _) => {
|
|
||||||
// Ignore
|
|
||||||
}
|
|
||||||
_ => {
|
_ => {
|
||||||
ret = Some(action);
|
ret = Some(action);
|
||||||
}
|
}
|
||||||
@@ -636,7 +640,7 @@ pub fn function_diff_ui(
|
|||||||
let right_diff_symbol = right_ctx.and_then(|ctx| {
|
let right_diff_symbol = right_ctx.and_then(|ctx| {
|
||||||
ctx.symbol_ref.and_then(|symbol_ref| ctx.diff.symbol_diff(symbol_ref).target_symbol)
|
ctx.symbol_ref.and_then(|symbol_ref| ctx.diff.symbol_diff(symbol_ref).target_symbol)
|
||||||
});
|
});
|
||||||
if left_diff_symbol.is_some() && right_ctx.map_or(false, |ctx| !ctx.has_symbol()) {
|
if left_diff_symbol.is_some() && right_ctx.is_some_and(|ctx| !ctx.has_symbol()) {
|
||||||
let (right_section, right_symbol) =
|
let (right_section, right_symbol) =
|
||||||
right_ctx.unwrap().obj.section_symbol(left_diff_symbol.unwrap());
|
right_ctx.unwrap().obj.section_symbol(left_diff_symbol.unwrap());
|
||||||
let symbol_ref = SymbolRefByName::new(right_symbol, right_section);
|
let symbol_ref = SymbolRefByName::new(right_symbol, right_section);
|
||||||
@@ -646,7 +650,7 @@ pub fn function_diff_ui(
|
|||||||
left_symbol: state.symbol_state.left_symbol.clone(),
|
left_symbol: state.symbol_state.left_symbol.clone(),
|
||||||
right_symbol: Some(symbol_ref),
|
right_symbol: Some(symbol_ref),
|
||||||
}));
|
}));
|
||||||
} else if right_diff_symbol.is_some() && left_ctx.map_or(false, |ctx| !ctx.has_symbol()) {
|
} else if right_diff_symbol.is_some() && left_ctx.is_some_and(|ctx| !ctx.has_symbol()) {
|
||||||
let (left_section, left_symbol) =
|
let (left_section, left_symbol) =
|
||||||
left_ctx.unwrap().obj.section_symbol(right_diff_symbol.unwrap());
|
left_ctx.unwrap().obj.section_symbol(right_diff_symbol.unwrap());
|
||||||
let symbol_ref = SymbolRefByName::new(left_symbol, left_section);
|
let symbol_ref = SymbolRefByName::new(left_symbol, left_section);
|
||||||
@@ -659,8 +663,8 @@ pub fn function_diff_ui(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If both sides are missing a symbol, switch to symbol diff view
|
// If both sides are missing a symbol, switch to symbol diff view
|
||||||
if right_ctx.map_or(false, |ctx| !ctx.has_symbol())
|
if right_ctx.is_some_and(|ctx| !ctx.has_symbol())
|
||||||
&& left_ctx.map_or(false, |ctx| !ctx.has_symbol())
|
&& left_ctx.is_some_and(|ctx| !ctx.has_symbol())
|
||||||
{
|
{
|
||||||
return Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff()));
|
return Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff()));
|
||||||
}
|
}
|
||||||
@@ -671,7 +675,7 @@ pub fn function_diff_ui(
|
|||||||
if column == 0 {
|
if column == 0 {
|
||||||
// Left column
|
// Left column
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
if ui.button("⏴ Back").clicked() {
|
if ui.button("⏴ Back").clicked() || hotkeys::back_pressed(ui.ctx()) {
|
||||||
ret = Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff()));
|
ret = Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff()));
|
||||||
}
|
}
|
||||||
ui.separator();
|
ui.separator();
|
||||||
@@ -679,7 +683,7 @@ pub fn function_diff_ui(
|
|||||||
.add_enabled(
|
.add_enabled(
|
||||||
!state.scratch_running
|
!state.scratch_running
|
||||||
&& state.scratch_available
|
&& state.scratch_available
|
||||||
&& left_ctx.map_or(false, |ctx| ctx.has_symbol()),
|
&& left_ctx.is_some_and(|ctx| ctx.has_symbol()),
|
||||||
egui::Button::new("📲 decomp.me"),
|
egui::Button::new("📲 decomp.me"),
|
||||||
)
|
)
|
||||||
.on_hover_text_at_pointer("Create a new scratch on decomp.me (beta)")
|
.on_hover_text_at_pointer("Create a new scratch on decomp.me (beta)")
|
||||||
@@ -703,11 +707,12 @@ pub fn function_diff_ui(
|
|||||||
.font(appearance.code_font.clone())
|
.font(appearance.code_font.clone())
|
||||||
.color(appearance.highlight_color),
|
.color(appearance.highlight_color),
|
||||||
);
|
);
|
||||||
if right_ctx.map_or(false, |m| m.has_symbol())
|
if right_ctx.is_some_and(|m| m.has_symbol())
|
||||||
&& ui
|
&& (ui
|
||||||
.button("Change target")
|
.button("Change target")
|
||||||
.on_hover_text_at_pointer("Choose a different symbol to use as the target")
|
.on_hover_text_at_pointer("Choose a different symbol to use as the target")
|
||||||
.clicked()
|
.clicked()
|
||||||
|
|| hotkeys::consume_change_target_shortcut(ui.ctx()))
|
||||||
{
|
{
|
||||||
if let Some(symbol_ref) = state.symbol_state.right_symbol.as_ref() {
|
if let Some(symbol_ref) = state.symbol_state.right_symbol.as_ref() {
|
||||||
ret = Some(DiffViewAction::SelectingLeft(symbol_ref.clone()));
|
ret = Some(DiffViewAction::SelectingLeft(symbol_ref.clone()));
|
||||||
@@ -773,7 +778,7 @@ pub fn function_diff_ui(
|
|||||||
.color(match_color_for_symbol(match_percent, appearance)),
|
.color(match_color_for_symbol(match_percent, appearance)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if left_ctx.map_or(false, |m| m.has_symbol()) {
|
if left_ctx.is_some_and(|m| m.has_symbol()) {
|
||||||
ui.separator();
|
ui.separator();
|
||||||
if ui
|
if ui
|
||||||
.button("Change base")
|
.button("Change base")
|
||||||
@@ -781,6 +786,7 @@ pub fn function_diff_ui(
|
|||||||
"Choose a different symbol to use as the base",
|
"Choose a different symbol to use as the base",
|
||||||
)
|
)
|
||||||
.clicked()
|
.clicked()
|
||||||
|
|| hotkeys::consume_change_base_shortcut(ui.ctx())
|
||||||
{
|
{
|
||||||
if let Some(symbol_ref) = state.symbol_state.left_symbol.as_ref() {
|
if let Some(symbol_ref) = state.symbol_state.left_symbol.as_ref() {
|
||||||
ret = Some(DiffViewAction::SelectingRight(symbol_ref.clone()));
|
ret = Some(DiffViewAction::SelectingRight(symbol_ref.clone()));
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use std::cmp::Ordering;
|
|||||||
use egui::{ProgressBar, RichText, Widget};
|
use egui::{ProgressBar, RichText, Widget};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
hotkeys,
|
||||||
jobs::{JobQueue, JobStatus},
|
jobs::{JobQueue, JobStatus},
|
||||||
views::appearance::Appearance,
|
views::appearance::Appearance,
|
||||||
};
|
};
|
||||||
@@ -94,7 +95,14 @@ impl From<&JobStatus> for JobStatusDisplay {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn jobs_menu_ui(ui: &mut egui::Ui, jobs: &mut JobQueue, appearance: &Appearance) -> bool {
|
pub fn jobs_menu_ui(ui: &mut egui::Ui, jobs: &mut JobQueue, appearance: &Appearance) -> bool {
|
||||||
ui.label("Jobs:");
|
let mut clicked = false;
|
||||||
|
if egui::Label::new(hotkeys::alt_text(ui, "_Jobs:", true))
|
||||||
|
.sense(egui::Sense::click())
|
||||||
|
.ui(ui)
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
clicked = true;
|
||||||
|
}
|
||||||
let mut statuses = Vec::new();
|
let mut statuses = Vec::new();
|
||||||
for job in jobs.iter_mut() {
|
for job in jobs.iter_mut() {
|
||||||
let Ok(status) = job.context.status.read() else {
|
let Ok(status) = job.context.status.read() else {
|
||||||
@@ -105,7 +113,6 @@ pub fn jobs_menu_ui(ui: &mut egui::Ui, jobs: &mut JobQueue, appearance: &Appeara
|
|||||||
let running_jobs = statuses.iter().filter(|s| !s.error).count();
|
let running_jobs = statuses.iter().filter(|s| !s.error).count();
|
||||||
let error_jobs = statuses.iter().filter(|s| s.error).count();
|
let error_jobs = statuses.iter().filter(|s| s.error).count();
|
||||||
|
|
||||||
let mut clicked = false;
|
|
||||||
let spinner =
|
let spinner =
|
||||||
egui::Spinner::new().size(appearance.ui_font.size * 0.9).color(appearance.text_color);
|
egui::Spinner::new().size(appearance.ui_font.size * 0.9).color(appearance.text_color);
|
||||||
match running_jobs.cmp(&1) {
|
match running_jobs.cmp(&1) {
|
||||||
|
|||||||
@@ -1,18 +1,21 @@
|
|||||||
use std::{collections::BTreeMap, mem::take};
|
use std::{collections::BTreeMap, mem::take, ops::Bound};
|
||||||
|
|
||||||
use egui::{
|
use egui::{
|
||||||
text::LayoutJob, CollapsingHeader, Color32, Id, OpenUrl, ScrollArea, SelectableLabel, TextEdit,
|
style::ScrollAnimation, text::LayoutJob, CollapsingHeader, Color32, Id, OpenUrl, ScrollArea,
|
||||||
Ui, Widget,
|
SelectableLabel, TextEdit, Ui, Widget,
|
||||||
};
|
};
|
||||||
use objdiff_core::{
|
use objdiff_core::{
|
||||||
arch::ObjArch,
|
arch::ObjArch,
|
||||||
diff::{display::HighlightKind, ObjDiff, ObjSymbolDiff},
|
diff::{display::HighlightKind, ObjDiff, ObjSymbolDiff},
|
||||||
obj::{ObjInfo, ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolFlags, SymbolRef},
|
obj::{
|
||||||
|
ObjInfo, ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolFlags, SymbolRef, SECTION_COMMON,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use regex::{Regex, RegexBuilder};
|
use regex::{Regex, RegexBuilder};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::AppStateRef,
|
app::AppStateRef,
|
||||||
|
hotkeys,
|
||||||
jobs::{
|
jobs::{
|
||||||
create_scratch::{start_create_scratch, CreateScratchConfig, CreateScratchResult},
|
create_scratch::{start_create_scratch, CreateScratchConfig, CreateScratchResult},
|
||||||
objdiff::{BuildStatus, ObjDiffResult},
|
objdiff::{BuildStatus, ObjDiffResult},
|
||||||
@@ -54,8 +57,8 @@ pub enum DiffViewAction {
|
|||||||
Build,
|
Build,
|
||||||
/// Navigate to a new diff view
|
/// Navigate to a new diff view
|
||||||
Navigate(DiffViewNavigation),
|
Navigate(DiffViewNavigation),
|
||||||
/// Set the highlighted symbols in the symbols view
|
/// Set the highlighted symbols in the symbols view, optionally scrolling them into view.
|
||||||
SetSymbolHighlight(Option<SymbolRef>, Option<SymbolRef>),
|
SetSymbolHighlight(Option<SymbolRef>, Option<SymbolRef>, bool),
|
||||||
/// Set the symbols view search filter
|
/// Set the symbols view search filter
|
||||||
SetSearch(String),
|
SetSearch(String),
|
||||||
/// Submit the current function to decomp.me
|
/// Submit the current function to decomp.me
|
||||||
@@ -133,6 +136,7 @@ pub struct DiffViewState {
|
|||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct SymbolViewState {
|
pub struct SymbolViewState {
|
||||||
pub highlighted_symbol: (Option<SymbolRef>, Option<SymbolRef>),
|
pub highlighted_symbol: (Option<SymbolRef>, Option<SymbolRef>),
|
||||||
|
pub autoscroll_to_highlighted_symbols: bool,
|
||||||
pub left_symbol: Option<SymbolRefByName>,
|
pub left_symbol: Option<SymbolRefByName>,
|
||||||
pub right_symbol: Option<SymbolRefByName>,
|
pub right_symbol: Option<SymbolRefByName>,
|
||||||
pub reverse_fn_order: bool,
|
pub reverse_fn_order: bool,
|
||||||
@@ -195,6 +199,9 @@ impl DiffViewState {
|
|||||||
ctx.output_mut(|o| o.open_url = Some(OpenUrl::new_tab(result.scratch_url)));
|
ctx.output_mut(|o| o.open_url = Some(OpenUrl::new_tab(result.scratch_url)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear the autoscroll flag so that it doesn't scroll continuously.
|
||||||
|
self.symbol_state.autoscroll_to_highlighted_symbols = false;
|
||||||
|
|
||||||
let Some(action) = action else {
|
let Some(action) = action else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -209,7 +216,6 @@ impl DiffViewState {
|
|||||||
// Ignore action if we're already navigating
|
// Ignore action if we're already navigating
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.symbol_state.highlighted_symbol = (None, None);
|
|
||||||
let Ok(mut state) = state.write() else {
|
let Ok(mut state) = state.write() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -245,8 +251,9 @@ impl DiffViewState {
|
|||||||
self.post_build_nav = Some(nav);
|
self.post_build_nav = Some(nav);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DiffViewAction::SetSymbolHighlight(left, right) => {
|
DiffViewAction::SetSymbolHighlight(left, right, autoscroll) => {
|
||||||
self.symbol_state.highlighted_symbol = (left, right);
|
self.symbol_state.highlighted_symbol = (left, right);
|
||||||
|
self.symbol_state.autoscroll_to_highlighted_symbols = autoscroll;
|
||||||
}
|
}
|
||||||
DiffViewAction::SetSearch(search) => {
|
DiffViewAction::SetSearch(search) => {
|
||||||
self.search_regex = if search.is_empty() {
|
self.search_regex = if search.is_empty() {
|
||||||
@@ -532,7 +539,15 @@ fn symbol_ui(
|
|||||||
ret = Some(DiffViewAction::Navigate(result));
|
ret = Some(DiffViewAction::Navigate(result));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if response.clicked() {
|
if selected && state.autoscroll_to_highlighted_symbols {
|
||||||
|
// Automatically scroll the view to encompass the selected symbol in case the user selected
|
||||||
|
// an offscreen symbol by using a keyboard shortcut.
|
||||||
|
ui.scroll_to_rect_animation(response.rect, None, ScrollAnimation::none());
|
||||||
|
// This autoscroll state flag will be reset in DiffViewState::post_update at the end of
|
||||||
|
// every frame so that we don't continuously scroll the view back when the user is trying to
|
||||||
|
// manually scroll away.
|
||||||
|
}
|
||||||
|
if response.clicked() || (selected && hotkeys::enter_pressed(ui.ctx())) {
|
||||||
if let Some(section) = section {
|
if let Some(section) = section {
|
||||||
match section.kind {
|
match section.kind {
|
||||||
ObjSectionKind::Code => {
|
ObjSectionKind::Code => {
|
||||||
@@ -559,20 +574,18 @@ fn symbol_ui(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if response.hovered() {
|
} else if response.hovered() {
|
||||||
ret = Some(if let Some(target_symbol) = symbol_diff.target_symbol {
|
ret = Some(if column == 0 {
|
||||||
if column == 0 {
|
DiffViewAction::SetSymbolHighlight(
|
||||||
DiffViewAction::SetSymbolHighlight(
|
Some(symbol_diff.symbol_ref),
|
||||||
Some(symbol_diff.symbol_ref),
|
symbol_diff.target_symbol,
|
||||||
Some(target_symbol),
|
false,
|
||||||
)
|
)
|
||||||
} else {
|
|
||||||
DiffViewAction::SetSymbolHighlight(
|
|
||||||
Some(target_symbol),
|
|
||||||
Some(symbol_diff.symbol_ref),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
DiffViewAction::SetSymbolHighlight(None, None)
|
DiffViewAction::SetSymbolHighlight(
|
||||||
|
symbol_diff.target_symbol,
|
||||||
|
Some(symbol_diff.symbol_ref),
|
||||||
|
false,
|
||||||
|
)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
ret
|
ret
|
||||||
@@ -646,16 +659,68 @@ pub fn symbol_list_ui(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hotkeys::check_scroll_hotkeys(ui, false);
|
||||||
|
|
||||||
|
let mut new_key_value_to_highlight = None;
|
||||||
|
if let Some(sym_ref) =
|
||||||
|
if column == 0 { state.highlighted_symbol.0 } else { state.highlighted_symbol.1 }
|
||||||
|
{
|
||||||
|
let up = if hotkeys::consume_up_key(ui.ctx()) {
|
||||||
|
Some(true)
|
||||||
|
} else if hotkeys::consume_down_key(ui.ctx()) {
|
||||||
|
Some(false)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
if let Some(mut up) = up {
|
||||||
|
if state.reverse_fn_order {
|
||||||
|
up = !up;
|
||||||
|
}
|
||||||
|
new_key_value_to_highlight = if up {
|
||||||
|
mapping.range(..sym_ref).next_back()
|
||||||
|
} else {
|
||||||
|
mapping.range((Bound::Excluded(sym_ref), Bound::Unbounded)).next()
|
||||||
|
};
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// No symbol is highlighted in this column. Select the topmost symbol instead.
|
||||||
|
// Note that we intentionally do not consume the up/down key presses in this case, but
|
||||||
|
// we do when a symbol is highlighted. This is so that if only one column has a symbol
|
||||||
|
// highlighted, that one takes precedence over the one with nothing highlighted.
|
||||||
|
if hotkeys::up_pressed(ui.ctx()) || hotkeys::down_pressed(ui.ctx()) {
|
||||||
|
new_key_value_to_highlight = if state.reverse_fn_order {
|
||||||
|
mapping.last_key_value()
|
||||||
|
} else {
|
||||||
|
mapping.first_key_value()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some((new_sym_ref, new_symbol_diff)) = new_key_value_to_highlight {
|
||||||
|
ret = Some(if column == 0 {
|
||||||
|
DiffViewAction::SetSymbolHighlight(
|
||||||
|
Some(*new_sym_ref),
|
||||||
|
new_symbol_diff.target_symbol,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
DiffViewAction::SetSymbolHighlight(
|
||||||
|
new_symbol_diff.target_symbol,
|
||||||
|
Some(*new_sym_ref),
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
ui.scope(|ui| {
|
ui.scope(|ui| {
|
||||||
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
|
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
|
||||||
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
|
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
|
||||||
|
|
||||||
// Skip sections with all symbols filtered out
|
// Skip sections with all symbols filtered out
|
||||||
if mapping.keys().any(|symbol_ref| symbol_ref.section_idx == usize::MAX) {
|
if mapping.keys().any(|symbol_ref| symbol_ref.section_idx == SECTION_COMMON) {
|
||||||
CollapsingHeader::new(".comm").default_open(true).show(ui, |ui| {
|
CollapsingHeader::new(".comm").default_open(true).show(ui, |ui| {
|
||||||
for (symbol_ref, symbol_diff) in mapping
|
for (symbol_ref, symbol_diff) in mapping
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(symbol_ref, _)| symbol_ref.section_idx == usize::MAX)
|
.filter(|(symbol_ref, _)| symbol_ref.section_idx == SECTION_COMMON)
|
||||||
{
|
{
|
||||||
let symbol = ctx.obj.section_symbol(*symbol_ref).1;
|
let symbol = ctx.obj.section_symbol(*symbol_ref).1;
|
||||||
if let Some(result) = symbol_ui(
|
if let Some(result) = symbol_ui(
|
||||||
@@ -836,7 +901,13 @@ pub fn symbol_diff_ui(
|
|||||||
});
|
});
|
||||||
|
|
||||||
let mut search = state.search.clone();
|
let mut search = state.search.clone();
|
||||||
if TextEdit::singleline(&mut search).hint_text("Filter symbols").ui(ui).changed() {
|
let response = TextEdit::singleline(&mut search)
|
||||||
|
.hint_text(hotkeys::alt_text(ui, "Filter _symbols", false))
|
||||||
|
.ui(ui);
|
||||||
|
if hotkeys::consume_symbol_filter_shortcut(ui.ctx()) {
|
||||||
|
response.request_focus();
|
||||||
|
}
|
||||||
|
if response.changed() {
|
||||||
ret = Some(DiffViewAction::SetSearch(search));
|
ret = Some(DiffViewAction::SetSearch(search));
|
||||||
}
|
}
|
||||||
} else if column == 1 {
|
} else if column == 1 {
|
||||||
|
|||||||
Reference in New Issue
Block a user