mirror of
https://github.com/encounter/objdiff.git
synced 2026-07-10 12:18:36 -07:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3970bc8acf | |||
| eaf0fabc2d | |||
| 91d11c83d6 | |||
| 94924047b7 | |||
| f5f6869029 | |||
| b02e32f2b7 |
Generated
+79
-5
@@ -508,6 +508,16 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bstr"
|
||||||
|
version = "1.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6798148dccfbff0fae41c7574d2fa8f1ef3492fba0face179de5d8d447d67b05"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.12.0"
|
version = "3.12.0"
|
||||||
@@ -885,7 +895,16 @@ version = "4.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059"
|
checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dirs-sys",
|
"dirs-sys 0.3.7",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dirs"
|
||||||
|
version = "5.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
|
||||||
|
dependencies = [
|
||||||
|
"dirs-sys 0.4.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -899,6 +918,18 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dirs-sys"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"option-ext",
|
||||||
|
"redox_users",
|
||||||
|
"windows-sys 0.48.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dirs-sys-next"
|
name = "dirs-sys-next"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
@@ -1455,6 +1486,20 @@ version = "0.3.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
|
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "globset"
|
||||||
|
version = "0.4.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "759c97c1e17c55525b57192c06a267cda0ac5210b222d6b82189a2338fa1c13d"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"bstr",
|
||||||
|
"fnv",
|
||||||
|
"log",
|
||||||
|
"regex",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "glow"
|
name = "glow"
|
||||||
version = "0.12.1"
|
version = "0.12.1"
|
||||||
@@ -2412,7 +2457,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objdiff"
|
name = "objdiff"
|
||||||
version = "0.3.3"
|
version = "0.4.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
@@ -2421,11 +2466,13 @@ dependencies = [
|
|||||||
"console_error_panic_hook",
|
"console_error_panic_hook",
|
||||||
"const_format",
|
"const_format",
|
||||||
"cwdemangle",
|
"cwdemangle",
|
||||||
|
"dirs 5.0.1",
|
||||||
"eframe",
|
"eframe",
|
||||||
"egui",
|
"egui",
|
||||||
"egui_extras",
|
"egui_extras",
|
||||||
"exec",
|
"exec",
|
||||||
"flagset",
|
"flagset",
|
||||||
|
"globset",
|
||||||
"log",
|
"log",
|
||||||
"memmap2 0.7.1",
|
"memmap2 0.7.1",
|
||||||
"notify",
|
"notify",
|
||||||
@@ -2438,6 +2485,8 @@ dependencies = [
|
|||||||
"rfd",
|
"rfd",
|
||||||
"self_update",
|
"self_update",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"serde_yaml",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"time",
|
"time",
|
||||||
@@ -2519,6 +2568,12 @@ dependencies = [
|
|||||||
"vcpkg",
|
"vcpkg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "option-ext"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "orbclient"
|
name = "orbclient"
|
||||||
version = "0.3.45"
|
version = "0.3.45"
|
||||||
@@ -3131,9 +3186,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.100"
|
version = "1.0.104"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0f1e14e89be7aa4c4b78bdbdc9eb5bf8517829a600ae8eaa39a6e1d960b5185c"
|
checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"ryu",
|
"ryu",
|
||||||
@@ -3172,6 +3227,19 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_yaml"
|
||||||
|
version = "0.9.25"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1a49e178e4452f45cb61d0cd8cebc1b0fafd3e41929e996cef79aa3aca91f574"
|
||||||
|
dependencies = [
|
||||||
|
"indexmap 2.0.0",
|
||||||
|
"itoa",
|
||||||
|
"ryu",
|
||||||
|
"serde",
|
||||||
|
"unsafe-libyaml",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sha1"
|
name = "sha1"
|
||||||
version = "0.10.5"
|
version = "0.10.5"
|
||||||
@@ -3701,6 +3769,12 @@ version = "0.2.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
|
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unsafe-libyaml"
|
||||||
|
version = "0.2.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "untrusted"
|
name = "untrusted"
|
||||||
version = "0.7.1"
|
version = "0.7.1"
|
||||||
@@ -3965,7 +4039,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "579cc485bd5ce5bfa0d738e4921dd0b956eca9800be1fd2e5257ebe95bc4617e"
|
checksum = "579cc485bd5ce5bfa0d738e4921dd0b956eca9800be1fd2e5257ebe95bc4617e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"core-foundation",
|
"core-foundation",
|
||||||
"dirs",
|
"dirs 4.0.0",
|
||||||
"jni",
|
"jni",
|
||||||
"log",
|
"log",
|
||||||
"ndk-context",
|
"ndk-context",
|
||||||
|
|||||||
+6
-2
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "objdiff"
|
name = "objdiff"
|
||||||
version = "0.3.3"
|
version = "0.4.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.65"
|
rust-version = "1.65"
|
||||||
authors = ["Luke Street <luke@street.dev>"]
|
authors = ["Luke Street <luke@street.dev>"]
|
||||||
@@ -23,14 +23,17 @@ wgpu = ["eframe/wgpu"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.71"
|
anyhow = "1.0.71"
|
||||||
|
byteorder = "1.4.3"
|
||||||
bytes = "1.4.0"
|
bytes = "1.4.0"
|
||||||
cfg-if = "1.0.0"
|
cfg-if = "1.0.0"
|
||||||
const_format = "0.2.31"
|
const_format = "0.2.31"
|
||||||
cwdemangle = "0.1.5"
|
cwdemangle = "0.1.5"
|
||||||
|
dirs = "5.0.1"
|
||||||
eframe = { version = "0.22.0", features = ["persistence"] }
|
eframe = { version = "0.22.0", features = ["persistence"] }
|
||||||
egui = "0.22.0"
|
egui = "0.22.0"
|
||||||
egui_extras = "0.22.0"
|
egui_extras = "0.22.0"
|
||||||
flagset = "0.4.3"
|
flagset = "0.4.3"
|
||||||
|
globset = { version = "0.4.13", features = ["serde1"] }
|
||||||
log = "0.4.19"
|
log = "0.4.19"
|
||||||
memmap2 = "0.7.1"
|
memmap2 = "0.7.1"
|
||||||
notify = "6.0.1"
|
notify = "6.0.1"
|
||||||
@@ -40,12 +43,13 @@ ppc750cl = { git = "https://github.com/terorie/ppc750cl", rev = "9ae36eef34aa6d7
|
|||||||
rabbitizer = "1.7.4"
|
rabbitizer = "1.7.4"
|
||||||
rfd = { version = "0.11.4" } #, default-features = false, features = ['xdg-portal']
|
rfd = { version = "0.11.4" } #, default-features = false, features = ['xdg-portal']
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
serde_json = "1.0.104"
|
||||||
|
serde_yaml = "0.9.25"
|
||||||
tempfile = "3.6.0"
|
tempfile = "3.6.0"
|
||||||
thiserror = "1.0.41"
|
thiserror = "1.0.41"
|
||||||
time = { version = "0.3.22", features = ["formatting", "local-offset"] }
|
time = { version = "0.3.22", features = ["formatting", "local-offset"] }
|
||||||
toml = "0.7.6"
|
toml = "0.7.6"
|
||||||
twox-hash = "1.6.3"
|
twox-hash = "1.6.3"
|
||||||
byteorder = "1.4.3"
|
|
||||||
|
|
||||||
# For Linux static binaries, use rustls
|
# For Linux static binaries, use rustls
|
||||||
[target.'cfg(target_os = "linux")'.dependencies]
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
|
|||||||
@@ -5,14 +5,87 @@
|
|||||||
|
|
||||||
A local diffing tool for decompilation projects.
|
A local diffing tool for decompilation projects.
|
||||||
|
|
||||||
Currently supports:
|
Supports:
|
||||||
- PowerPC 750CL (GameCube & Wii)
|
- PowerPC 750CL (GameCube & Wii)
|
||||||
- MIPS (Nintendo 64)
|
- MIPS (Nintendo 64)
|
||||||
|
|
||||||
|
See [Usage](#usage) for more information.
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
### License
|
## Usage
|
||||||
|
|
||||||
|
objdiff works by comparing two relocatable object files (`.o`). The objects are expected to have the same relative path from the "target" and "base" directories.
|
||||||
|
|
||||||
|
For example, if the target ("expected") object is located at `build/asm/MetroTRK/mslsupp.o` and the base ("actual") object
|
||||||
|
is located at `build/src/MetroTRK/mslsupp.o`, the following configuration would be used:
|
||||||
|
|
||||||
|
- Target build directory: `build/asm`
|
||||||
|
- Base build directory: `build/src`
|
||||||
|
- Object: `MetroTRK/mslsupp.o`
|
||||||
|
|
||||||
|
objdiff will then execute the build system from the project directory to build both objects:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ make build/asm/MetroTRK/mslsupp.o # Only if "Build target object" is enabled
|
||||||
|
$ make build/src/MetroTRK/mslsupp.o
|
||||||
|
```
|
||||||
|
|
||||||
|
The objects will then be compared and the results will be displayed in the UI.
|
||||||
|
|
||||||
|
See [Configuration](#configuration) for more information.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
While **not required** (most settings can be specified in the UI), projects can add an `objdiff.json` (or `objdiff.yaml`, `objdiff.yml`) file to configure the tool automatically. The configuration file must be located in the root project directory.
|
||||||
|
|
||||||
|
```json5
|
||||||
|
// objdiff.json
|
||||||
|
{
|
||||||
|
"custom_make": "ninja",
|
||||||
|
"target_dir": "build/mp1.0/asm",
|
||||||
|
"base_dir": "build/mp1.0/src",
|
||||||
|
"build_target": true,
|
||||||
|
"watch_patterns": [
|
||||||
|
"*.c",
|
||||||
|
"*.cp",
|
||||||
|
"*.cpp",
|
||||||
|
"*.h",
|
||||||
|
"*.hpp",
|
||||||
|
"*.py"
|
||||||
|
],
|
||||||
|
"objects": [
|
||||||
|
{
|
||||||
|
"path": "MetroTRK/mslsupp.o",
|
||||||
|
"name": "MetroTRK/mslsupp",
|
||||||
|
"reverse_fn_order": false
|
||||||
|
},
|
||||||
|
// ...
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `custom_make` _(optional)_: By default, objdiff will use `make` to build the project.
|
||||||
|
If the project uses a different build system (e.g. `ninja`), specify it here.
|
||||||
|
- `target_dir`: Relative from the root of the project, this where the "target" or "expected" objects are located.
|
||||||
|
These are the **intended result** of the match.
|
||||||
|
- `base_dir`: Relative from the root of the project, this is where the "base" or "actual" objects are located.
|
||||||
|
These are objects built from the **current source code**.
|
||||||
|
- `build_target`: If true, objdiff will tell the build system to build the target objects before diffing (e.g. `make path/to/target.o`).
|
||||||
|
This is useful if the target objects are not built by default or can change based on project configuration or edits to assembly files.
|
||||||
|
Requires the build system to be configured properly.
|
||||||
|
- `watch_patterns` _(optional)_: A list of glob patterns to watch for changes. ([Supported syntax](https://docs.rs/globset/latest/globset/#syntax))
|
||||||
|
If any of these files change, objdiff will automatically rebuild the objects and re-compare them.
|
||||||
|
- `objects` _(optional)_: If specified, objdiff will display a list of objects in the sidebar for easy navigation.
|
||||||
|
- `path`: Relative path to the object from the `target_dir` and `base_dir`.
|
||||||
|
- `name` _(optional)_: The name of the object in the UI. If not specified, the object's `path` will be used.
|
||||||
|
- `reverse_fn_order` _(optional)_: Displays function symbols in reversed order.
|
||||||
|
Used to support MWCC's `-inline deferred` option, which reverses the order of functions in the object file.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
Licensed under either of
|
Licensed under either of
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 133 KiB After Width: | Height: | Size: 86 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 158 KiB After Width: | Height: | Size: 144 KiB |
+269
-408
File diff suppressed because it is too large
Load Diff
+126
@@ -0,0 +1,126 @@
|
|||||||
|
use std::{
|
||||||
|
fs::File,
|
||||||
|
path::{Component, Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use globset::{Glob, GlobSet, GlobSetBuilder};
|
||||||
|
|
||||||
|
use crate::app::AppConfig;
|
||||||
|
|
||||||
|
#[derive(Default, Clone, serde::Deserialize)]
|
||||||
|
#[serde(default)]
|
||||||
|
pub struct ProjectConfig {
|
||||||
|
pub custom_make: Option<String>,
|
||||||
|
pub target_dir: Option<PathBuf>,
|
||||||
|
pub base_dir: Option<PathBuf>,
|
||||||
|
pub build_target: bool,
|
||||||
|
pub watch_patterns: Vec<Glob>,
|
||||||
|
#[serde(alias = "units")]
|
||||||
|
pub objects: Vec<ProjectObject>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Clone, serde::Deserialize)]
|
||||||
|
pub struct ProjectObject {
|
||||||
|
pub name: Option<String>,
|
||||||
|
pub path: PathBuf,
|
||||||
|
pub reverse_fn_order: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum ProjectObjectNode {
|
||||||
|
File(String, ProjectObject),
|
||||||
|
Dir(String, Vec<ProjectObjectNode>),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_dir<'a>(
|
||||||
|
name: &str,
|
||||||
|
nodes: &'a mut Vec<ProjectObjectNode>,
|
||||||
|
) -> &'a mut Vec<ProjectObjectNode> {
|
||||||
|
if let Some(index) = nodes
|
||||||
|
.iter()
|
||||||
|
.position(|node| matches!(node, ProjectObjectNode::Dir(dir_name, _) if dir_name == name))
|
||||||
|
{
|
||||||
|
if let ProjectObjectNode::Dir(_, children) = &mut nodes[index] {
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
nodes.push(ProjectObjectNode::Dir(name.to_string(), vec![]));
|
||||||
|
if let Some(ProjectObjectNode::Dir(_, children)) = nodes.last_mut() {
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unreachable!();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_nodes(objects: &[ProjectObject]) -> Vec<ProjectObjectNode> {
|
||||||
|
let mut nodes = vec![];
|
||||||
|
for object in objects {
|
||||||
|
let mut out_nodes = &mut nodes;
|
||||||
|
let path = object.name.as_ref().map(Path::new).unwrap_or(&object.path);
|
||||||
|
if let Some(parent) = path.parent() {
|
||||||
|
for component in parent.components() {
|
||||||
|
if let Component::Normal(name) = component {
|
||||||
|
let name = name.to_str().unwrap();
|
||||||
|
out_nodes = find_dir(name, out_nodes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let filename = path.file_name().unwrap().to_str().unwrap().to_string();
|
||||||
|
out_nodes.push(ProjectObjectNode::File(filename, object.clone()));
|
||||||
|
}
|
||||||
|
nodes
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const CONFIG_FILENAMES: [&str; 3] = ["objdiff.yml", "objdiff.yaml", "objdiff.json"];
|
||||||
|
|
||||||
|
pub fn load_project_config(config: &mut AppConfig) -> Result<()> {
|
||||||
|
let Some(project_dir) = &config.project_dir else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
if let Some(result) = try_project_config(project_dir) {
|
||||||
|
let project_config = result?;
|
||||||
|
config.custom_make = project_config.custom_make;
|
||||||
|
config.target_obj_dir = project_config.target_dir.map(|p| project_dir.join(p));
|
||||||
|
config.base_obj_dir = project_config.base_dir.map(|p| project_dir.join(p));
|
||||||
|
config.build_target = project_config.build_target;
|
||||||
|
config.watch_patterns = project_config.watch_patterns;
|
||||||
|
config.watcher_change = true;
|
||||||
|
config.objects = project_config.objects;
|
||||||
|
config.object_nodes = build_nodes(&config.objects);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_project_config(dir: &Path) -> Option<Result<ProjectConfig>> {
|
||||||
|
for filename in CONFIG_FILENAMES.iter() {
|
||||||
|
let config_path = dir.join(filename);
|
||||||
|
if config_path.is_file() {
|
||||||
|
return match filename.contains("json") {
|
||||||
|
true => Some(read_json_config(&config_path)),
|
||||||
|
false => Some(read_yml_config(&config_path)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_yml_config(config_path: &Path) -> Result<ProjectConfig> {
|
||||||
|
let mut reader = File::open(config_path)
|
||||||
|
.with_context(|| format!("Failed to open config file '{}'", config_path.display()))?;
|
||||||
|
Ok(serde_yaml::from_reader(&mut reader)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_json_config(config_path: &Path) -> Result<ProjectConfig> {
|
||||||
|
let mut reader = File::open(config_path)
|
||||||
|
.with_context(|| format!("Failed to open config file '{}'", config_path.display()))?;
|
||||||
|
Ok(serde_json::from_reader(&mut reader)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_globset(vec: &[Glob]) -> std::result::Result<GlobSet, globset::Error> {
|
||||||
|
let mut builder = GlobSetBuilder::new();
|
||||||
|
for glob in vec {
|
||||||
|
builder.add(glob.clone());
|
||||||
|
}
|
||||||
|
builder.build()
|
||||||
|
}
|
||||||
+4
-3
@@ -3,7 +3,6 @@ use std::{collections::BTreeMap, mem::take};
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::DiffConfig,
|
|
||||||
editops::{editops_find, LevEditType},
|
editops::{editops_find, LevEditType},
|
||||||
obj::{
|
obj::{
|
||||||
mips, ppc, ObjArchitecture, ObjDataDiff, ObjDataDiffKind, ObjInfo, ObjInsArg,
|
mips, ppc, ObjArchitecture, ObjDataDiff, ObjDataDiffKind, ObjInfo, ObjInsArg,
|
||||||
@@ -373,7 +372,7 @@ fn find_section_and_symbol(obj: &ObjInfo, name: &str) -> Option<(usize, usize)>
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn diff_objs(left: &mut ObjInfo, right: &mut ObjInfo, _diff_config: &DiffConfig) -> Result<()> {
|
pub fn diff_objs(left: &mut ObjInfo, right: &mut ObjInfo) -> Result<()> {
|
||||||
for left_section in &mut left.sections {
|
for left_section in &mut left.sections {
|
||||||
if left_section.kind == ObjSectionKind::Code {
|
if left_section.kind == ObjSectionKind::Code {
|
||||||
for left_symbol in &mut left_section.symbols {
|
for left_symbol in &mut left_section.symbols {
|
||||||
@@ -406,7 +405,9 @@ pub fn diff_objs(left: &mut ObjInfo, right: &mut ObjInfo, _diff_config: &DiffCon
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let Some(right_section) = right.sections.iter_mut().find(|s| s.name == left_section.name) else {
|
let Some(right_section) =
|
||||||
|
right.sections.iter_mut().find(|s| s.name == left_section.name)
|
||||||
|
else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
if left_section.kind == ObjSectionKind::Data {
|
if left_section.kind == ObjSectionKind::Data {
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
use std::sync::{mpsc::Receiver, Arc, RwLock};
|
|
||||||
|
|
||||||
use anyhow::{Error, Result};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
app::{AppConfig, DiffConfig},
|
|
||||||
diff::diff_objs,
|
|
||||||
jobs::{queue_job, update_status, Job, JobResult, JobState, Status},
|
|
||||||
obj::{elf, ObjInfo},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct BinDiffResult {
|
|
||||||
pub first_obj: ObjInfo,
|
|
||||||
pub second_obj: ObjInfo,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_build(
|
|
||||||
status: &Status,
|
|
||||||
cancel: Receiver<()>,
|
|
||||||
config: Arc<RwLock<AppConfig>>,
|
|
||||||
) -> Result<Box<BinDiffResult>> {
|
|
||||||
let config = config.read().map_err(|_| Error::msg("Failed to lock app config"))?.clone();
|
|
||||||
let target_path =
|
|
||||||
config.left_obj.as_ref().ok_or_else(|| Error::msg("Missing target obj path"))?;
|
|
||||||
let base_path = config.right_obj.as_ref().ok_or_else(|| Error::msg("Missing base obj path"))?;
|
|
||||||
|
|
||||||
update_status(status, "Loading target obj".to_string(), 0, 3, &cancel)?;
|
|
||||||
let mut left_obj = elf::read(target_path)?;
|
|
||||||
|
|
||||||
update_status(status, "Loading base obj".to_string(), 1, 3, &cancel)?;
|
|
||||||
let mut right_obj = elf::read(base_path)?;
|
|
||||||
|
|
||||||
update_status(status, "Performing diff".to_string(), 2, 3, &cancel)?;
|
|
||||||
diff_objs(&mut left_obj, &mut right_obj, &DiffConfig::default() /* TODO */)?;
|
|
||||||
|
|
||||||
update_status(status, "Complete".to_string(), 3, 3, &cancel)?;
|
|
||||||
Ok(Box::new(BinDiffResult { first_obj: left_obj, second_obj: right_obj }))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn queue_bindiff(config: Arc<RwLock<AppConfig>>) -> JobState {
|
|
||||||
queue_job("Binary diff", Job::BinDiff, move |status, cancel| {
|
|
||||||
run_build(status, cancel, config).map(JobResult::BinDiff)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,7 @@ use anyhow::{Context, Result};
|
|||||||
use self_update::{cargo_crate_version, update::Release};
|
use self_update::{cargo_crate_version, update::Release};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
jobs::{queue_job, update_status, Job, JobResult, JobState, Status},
|
jobs::{start_job, update_status, Job, JobResult, JobState, JobStatusRef},
|
||||||
update::{build_updater, BIN_NAME},
|
update::{build_updater, BIN_NAME},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ pub struct CheckUpdateResult {
|
|||||||
pub found_binary: bool,
|
pub found_binary: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_check_update(status: &Status, cancel: Receiver<()>) -> Result<Box<CheckUpdateResult>> {
|
fn run_check_update(status: &JobStatusRef, cancel: Receiver<()>) -> Result<Box<CheckUpdateResult>> {
|
||||||
update_status(status, "Fetching latest release".to_string(), 0, 1, &cancel)?;
|
update_status(status, "Fetching latest release".to_string(), 0, 1, &cancel)?;
|
||||||
let updater = build_updater().context("Failed to create release updater")?;
|
let updater = build_updater().context("Failed to create release updater")?;
|
||||||
let latest_release = updater.get_latest_release()?;
|
let latest_release = updater.get_latest_release()?;
|
||||||
@@ -26,8 +26,8 @@ fn run_check_update(status: &Status, cancel: Receiver<()>) -> Result<Box<CheckUp
|
|||||||
Ok(Box::new(CheckUpdateResult { update_available, latest_release, found_binary }))
|
Ok(Box::new(CheckUpdateResult { update_available, latest_release, found_binary }))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn queue_check_update() -> JobState {
|
pub fn start_check_update() -> JobState {
|
||||||
queue_job("Check for updates", Job::CheckUpdate, move |status, cancel| {
|
start_job("Check for updates", Job::CheckUpdate, move |status, cancel| {
|
||||||
run_check_update(status, cancel).map(JobResult::CheckUpdate)
|
run_check_update(status, cancel).map(|result| JobResult::CheckUpdate(Some(result)))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+81
-18
@@ -9,12 +9,8 @@ use std::{
|
|||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
use crate::jobs::{
|
use crate::jobs::{check_update::CheckUpdateResult, objdiff::ObjDiffResult, update::UpdateResult};
|
||||||
bindiff::BinDiffResult, check_update::CheckUpdateResult, objdiff::ObjDiffResult,
|
|
||||||
update::UpdateResult,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub mod bindiff;
|
|
||||||
pub mod check_update;
|
pub mod check_update;
|
||||||
pub mod objdiff;
|
pub mod objdiff;
|
||||||
pub mod update;
|
pub mod update;
|
||||||
@@ -22,19 +18,88 @@ pub mod update;
|
|||||||
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
|
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
|
||||||
pub enum Job {
|
pub enum Job {
|
||||||
ObjDiff,
|
ObjDiff,
|
||||||
BinDiff,
|
|
||||||
CheckUpdate,
|
CheckUpdate,
|
||||||
Update,
|
Update,
|
||||||
}
|
}
|
||||||
pub static JOB_ID: AtomicUsize = AtomicUsize::new(0);
|
pub static JOB_ID: AtomicUsize = AtomicUsize::new(0);
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct JobQueue {
|
||||||
|
pub jobs: Vec<JobState>,
|
||||||
|
pub results: Vec<JobResult>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JobQueue {
|
||||||
|
/// Adds a job to the queue.
|
||||||
|
#[inline]
|
||||||
|
pub fn push(&mut self, state: JobState) { self.jobs.push(state); }
|
||||||
|
|
||||||
|
/// Adds a job to the queue if a job of the given kind is not already running.
|
||||||
|
#[inline]
|
||||||
|
pub fn push_once(&mut self, job: Job, func: impl FnOnce() -> JobState) {
|
||||||
|
if !self.is_running(job) {
|
||||||
|
self.push(func());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether a job of the given kind is running.
|
||||||
|
pub fn is_running(&self, kind: Job) -> bool {
|
||||||
|
self.jobs.iter().any(|j| j.kind == kind && j.handle.is_some())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether any job is running.
|
||||||
|
pub fn any_running(&self) -> bool {
|
||||||
|
self.jobs.iter().any(|job| {
|
||||||
|
if let Some(handle) = &job.handle {
|
||||||
|
return !handle.is_finished();
|
||||||
|
}
|
||||||
|
false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterates over all jobs mutably.
|
||||||
|
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut JobState> + '_ { self.jobs.iter_mut() }
|
||||||
|
|
||||||
|
/// Iterates over all finished jobs, returning the job state and the result.
|
||||||
|
pub fn iter_finished(
|
||||||
|
&mut self,
|
||||||
|
) -> impl Iterator<Item = (&mut JobState, std::thread::Result<JobResult>)> + '_ {
|
||||||
|
self.jobs.iter_mut().filter_map(|job| {
|
||||||
|
if let Some(handle) = &job.handle {
|
||||||
|
if !handle.is_finished() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let result = job.handle.take().unwrap().join();
|
||||||
|
return Some((job, result));
|
||||||
|
}
|
||||||
|
None
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clears all finished jobs.
|
||||||
|
pub fn clear_finished(&mut self) {
|
||||||
|
self.jobs.retain(|job| {
|
||||||
|
!(job.should_remove
|
||||||
|
&& job.handle.is_none()
|
||||||
|
&& job.status.read().unwrap().error.is_none())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes a job from the queue given its ID.
|
||||||
|
pub fn remove(&mut self, id: usize) { self.jobs.retain(|job| job.id != id); }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type JobStatusRef = Arc<RwLock<JobStatus>>;
|
||||||
|
|
||||||
pub struct JobState {
|
pub struct JobState {
|
||||||
pub id: usize,
|
pub id: usize,
|
||||||
pub job_type: Job,
|
pub kind: Job,
|
||||||
pub handle: Option<JoinHandle<JobResult>>,
|
pub handle: Option<JoinHandle<JobResult>>,
|
||||||
pub status: Arc<RwLock<JobStatus>>,
|
pub status: JobStatusRef,
|
||||||
pub cancel: Sender<()>,
|
pub cancel: Sender<()>,
|
||||||
pub should_remove: bool,
|
pub should_remove: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct JobStatus {
|
pub struct JobStatus {
|
||||||
pub title: String,
|
pub title: String,
|
||||||
@@ -43,11 +108,11 @@ pub struct JobStatus {
|
|||||||
pub status: String,
|
pub status: String,
|
||||||
pub error: Option<anyhow::Error>,
|
pub error: Option<anyhow::Error>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum JobResult {
|
pub enum JobResult {
|
||||||
None,
|
None,
|
||||||
ObjDiff(Box<ObjDiffResult>),
|
ObjDiff(Option<Box<ObjDiffResult>>),
|
||||||
BinDiff(Box<BinDiffResult>),
|
CheckUpdate(Option<Box<CheckUpdateResult>>),
|
||||||
CheckUpdate(Box<CheckUpdateResult>),
|
|
||||||
Update(Box<UpdateResult>),
|
Update(Box<UpdateResult>),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,12 +123,10 @@ fn should_cancel(rx: &Receiver<()>) -> bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Status = Arc<RwLock<JobStatus>>;
|
fn start_job(
|
||||||
|
|
||||||
fn queue_job(
|
|
||||||
title: &str,
|
title: &str,
|
||||||
job_type: Job,
|
kind: Job,
|
||||||
run: impl FnOnce(&Status, Receiver<()>) -> Result<JobResult> + Send + 'static,
|
run: impl FnOnce(&JobStatusRef, Receiver<()>) -> Result<JobResult> + Send + 'static,
|
||||||
) -> JobState {
|
) -> JobState {
|
||||||
let status = Arc::new(RwLock::new(JobStatus {
|
let status = Arc::new(RwLock::new(JobStatus {
|
||||||
title: title.to_string(),
|
title: title.to_string(),
|
||||||
@@ -89,7 +152,7 @@ fn queue_job(
|
|||||||
log::info!("Started job {}", id);
|
log::info!("Started job {}", id);
|
||||||
JobState {
|
JobState {
|
||||||
id,
|
id,
|
||||||
job_type,
|
kind,
|
||||||
handle: Some(handle),
|
handle: Some(handle),
|
||||||
status: status_clone,
|
status: status_clone,
|
||||||
cancel: tx,
|
cancel: tx,
|
||||||
@@ -98,7 +161,7 @@ fn queue_job(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn update_status(
|
fn update_status(
|
||||||
status: &Status,
|
status: &JobStatusRef,
|
||||||
str: String,
|
str: String,
|
||||||
count: u32,
|
count: u32,
|
||||||
total: u32,
|
total: u32,
|
||||||
|
|||||||
+9
-15
@@ -1,17 +1,12 @@
|
|||||||
use std::{
|
use std::{path::Path, process::Command, str::from_utf8, sync::mpsc::Receiver};
|
||||||
path::Path,
|
|
||||||
process::Command,
|
|
||||||
str::from_utf8,
|
|
||||||
sync::{mpsc::Receiver, Arc, RwLock},
|
|
||||||
};
|
|
||||||
|
|
||||||
use anyhow::{Context, Error, Result};
|
use anyhow::{Context, Error, Result};
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::{AppConfig, DiffConfig},
|
app::{AppConfig, AppConfigRef},
|
||||||
diff::diff_objs,
|
diff::diff_objs,
|
||||||
jobs::{queue_job, update_status, Job, JobResult, JobState, Status},
|
jobs::{start_job, update_status, Job, JobResult, JobState, JobStatusRef},
|
||||||
obj::{elf, ObjInfo},
|
obj::{elf, ObjInfo},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -76,10 +71,9 @@ fn run_make(cwd: &Path, arg: &Path, config: &AppConfig) -> BuildStatus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn run_build(
|
fn run_build(
|
||||||
status: &Status,
|
status: &JobStatusRef,
|
||||||
cancel: Receiver<()>,
|
cancel: Receiver<()>,
|
||||||
config: Arc<RwLock<AppConfig>>,
|
config: AppConfigRef,
|
||||||
diff_config: DiffConfig,
|
|
||||||
) -> Result<Box<ObjDiffResult>> {
|
) -> Result<Box<ObjDiffResult>> {
|
||||||
let config = config.read().map_err(|_| Error::msg("Failed to lock app config"))?.clone();
|
let config = config.read().map_err(|_| Error::msg("Failed to lock app config"))?.clone();
|
||||||
let obj_path = config.obj_path.as_ref().ok_or_else(|| Error::msg("Missing obj path"))?;
|
let obj_path = config.obj_path.as_ref().ok_or_else(|| Error::msg("Missing obj path"))?;
|
||||||
@@ -129,15 +123,15 @@ fn run_build(
|
|||||||
|
|
||||||
if let (Some(first_obj), Some(second_obj)) = (&mut first_obj, &mut second_obj) {
|
if let (Some(first_obj), Some(second_obj)) = (&mut first_obj, &mut second_obj) {
|
||||||
update_status(status, "Performing diff".to_string(), 4, total, &cancel)?;
|
update_status(status, "Performing diff".to_string(), 4, total, &cancel)?;
|
||||||
diff_objs(first_obj, second_obj, &diff_config)?;
|
diff_objs(first_obj, second_obj)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
update_status(status, "Complete".to_string(), total, total, &cancel)?;
|
update_status(status, "Complete".to_string(), total, total, &cancel)?;
|
||||||
Ok(Box::new(ObjDiffResult { first_status, second_status, first_obj, second_obj, time }))
|
Ok(Box::new(ObjDiffResult { first_status, second_status, first_obj, second_obj, time }))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn queue_build(config: Arc<RwLock<AppConfig>>, diff_config: DiffConfig) -> JobState {
|
pub fn start_build(config: AppConfigRef) -> JobState {
|
||||||
queue_job("Object diff", Job::ObjDiff, move |status, cancel| {
|
start_job("Object diff", Job::ObjDiff, move |status, cancel| {
|
||||||
run_build(status, cancel, config, diff_config).map(JobResult::ObjDiff)
|
run_build(status, cancel, config).map(|result| JobResult::ObjDiff(Some(result)))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -9,7 +9,7 @@ use anyhow::{Context, Result};
|
|||||||
use const_format::formatcp;
|
use const_format::formatcp;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
jobs::{queue_job, update_status, Job, JobResult, JobState, Status},
|
jobs::{start_job, update_status, Job, JobResult, JobState, JobStatusRef},
|
||||||
update::{build_updater, BIN_NAME},
|
update::{build_updater, BIN_NAME},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ pub struct UpdateResult {
|
|||||||
pub exe_path: PathBuf,
|
pub exe_path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_update(status: &Status, cancel: Receiver<()>) -> Result<Box<UpdateResult>> {
|
fn run_update(status: &JobStatusRef, cancel: Receiver<()>) -> Result<Box<UpdateResult>> {
|
||||||
update_status(status, "Fetching latest release".to_string(), 0, 3, &cancel)?;
|
update_status(status, "Fetching latest release".to_string(), 0, 3, &cancel)?;
|
||||||
let updater = build_updater().context("Failed to create release updater")?;
|
let updater = build_updater().context("Failed to create release updater")?;
|
||||||
let latest_release = updater.get_latest_release()?;
|
let latest_release = updater.get_latest_release()?;
|
||||||
@@ -53,8 +53,8 @@ fn run_update(status: &Status, cancel: Receiver<()>) -> Result<Box<UpdateResult>
|
|||||||
Ok(Box::from(UpdateResult { exe_path: target_file }))
|
Ok(Box::from(UpdateResult { exe_path: target_file }))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn queue_update() -> JobState {
|
pub fn start_update() -> JobState {
|
||||||
queue_job("Update app", Job::Update, move |status, cancel| {
|
start_job("Update app", Job::Update, move |status, cancel| {
|
||||||
run_update(status, cancel).map(JobResult::Update)
|
run_update(status, cancel).map(JobResult::Update)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
pub use app::App;
|
pub use app::App;
|
||||||
|
|
||||||
mod app;
|
mod app;
|
||||||
|
mod config;
|
||||||
mod diff;
|
mod diff;
|
||||||
mod editops;
|
mod editops;
|
||||||
mod jobs;
|
mod jobs;
|
||||||
|
|||||||
+2
-1
@@ -37,7 +37,8 @@ fn main() {
|
|||||||
|
|
||||||
let exec_path: Rc<Mutex<Option<PathBuf>>> = Rc::new(Mutex::new(None));
|
let exec_path: Rc<Mutex<Option<PathBuf>>> = Rc::new(Mutex::new(None));
|
||||||
let exec_path_clone = exec_path.clone();
|
let exec_path_clone = exec_path.clone();
|
||||||
let mut native_options = eframe::NativeOptions::default();
|
let mut native_options =
|
||||||
|
eframe::NativeOptions { follow_system_theme: false, ..Default::default() };
|
||||||
match load_icon() {
|
match load_icon() {
|
||||||
Ok(data) => {
|
Ok(data) => {
|
||||||
native_options.icon_data = Some(data);
|
native_options.icon_data = Some(data);
|
||||||
|
|||||||
+1
-1
@@ -181,7 +181,7 @@ fn find_section_symbol(
|
|||||||
fn relocations_by_section(
|
fn relocations_by_section(
|
||||||
arch: ObjArchitecture,
|
arch: ObjArchitecture,
|
||||||
obj_file: &File<'_>,
|
obj_file: &File<'_>,
|
||||||
section: &mut ObjSection,
|
section: &ObjSection,
|
||||||
) -> Result<Vec<ObjReloc>> {
|
) -> Result<Vec<ObjReloc>> {
|
||||||
let obj_section = obj_file.section_by_index(SectionIndex(section.index))?;
|
let obj_section = obj_file.section_by_index(SectionIndex(section.index))?;
|
||||||
let mut relocations = Vec::<ObjReloc>::new();
|
let mut relocations = Vec::<ObjReloc>::new();
|
||||||
|
|||||||
@@ -0,0 +1,139 @@
|
|||||||
|
use egui::{Color32, FontFamily, FontId, TextStyle};
|
||||||
|
use time::UtcOffset;
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, serde::Serialize)]
|
||||||
|
#[serde(default)]
|
||||||
|
pub struct Appearance {
|
||||||
|
pub ui_font: FontId,
|
||||||
|
pub code_font: FontId,
|
||||||
|
pub diff_colors: Vec<Color32>,
|
||||||
|
pub theme: eframe::Theme,
|
||||||
|
|
||||||
|
// Applied by theme
|
||||||
|
#[serde(skip)]
|
||||||
|
pub text_color: Color32, // GRAY
|
||||||
|
#[serde(skip)]
|
||||||
|
pub emphasized_text_color: Color32, // LIGHT_GRAY
|
||||||
|
#[serde(skip)]
|
||||||
|
pub deemphasized_text_color: Color32, // DARK_GRAY
|
||||||
|
#[serde(skip)]
|
||||||
|
pub highlight_color: Color32, // WHITE
|
||||||
|
#[serde(skip)]
|
||||||
|
pub replace_color: Color32, // LIGHT_BLUE
|
||||||
|
#[serde(skip)]
|
||||||
|
pub insert_color: Color32, // GREEN
|
||||||
|
#[serde(skip)]
|
||||||
|
pub delete_color: Color32, // RED
|
||||||
|
|
||||||
|
// Global
|
||||||
|
#[serde(skip)]
|
||||||
|
pub utc_offset: UtcOffset,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Appearance {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
ui_font: FontId { size: 12.0, family: FontFamily::Proportional },
|
||||||
|
code_font: FontId { size: 14.0, family: FontFamily::Monospace },
|
||||||
|
diff_colors: DEFAULT_COLOR_ROTATION.to_vec(),
|
||||||
|
theme: eframe::Theme::Dark,
|
||||||
|
text_color: Color32::GRAY,
|
||||||
|
emphasized_text_color: Color32::LIGHT_GRAY,
|
||||||
|
deemphasized_text_color: Color32::DARK_GRAY,
|
||||||
|
highlight_color: Color32::WHITE,
|
||||||
|
replace_color: Color32::LIGHT_BLUE,
|
||||||
|
insert_color: Color32::GREEN,
|
||||||
|
delete_color: Color32::from_rgb(200, 40, 41),
|
||||||
|
utc_offset: UtcOffset::UTC,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Appearance {
|
||||||
|
pub fn apply(&mut self, style: &egui::Style) -> egui::Style {
|
||||||
|
let mut style = style.clone();
|
||||||
|
style.text_styles.insert(TextStyle::Body, FontId {
|
||||||
|
size: (self.ui_font.size * 0.75).floor(),
|
||||||
|
family: self.ui_font.family.clone(),
|
||||||
|
});
|
||||||
|
style.text_styles.insert(TextStyle::Body, self.ui_font.clone());
|
||||||
|
style.text_styles.insert(TextStyle::Button, self.ui_font.clone());
|
||||||
|
style.text_styles.insert(TextStyle::Heading, FontId {
|
||||||
|
size: (self.ui_font.size * 1.5).floor(),
|
||||||
|
family: self.ui_font.family.clone(),
|
||||||
|
});
|
||||||
|
style.text_styles.insert(TextStyle::Monospace, self.code_font.clone());
|
||||||
|
match self.theme {
|
||||||
|
eframe::Theme::Dark => {
|
||||||
|
style.visuals = egui::Visuals::dark();
|
||||||
|
self.text_color = Color32::GRAY;
|
||||||
|
self.emphasized_text_color = Color32::LIGHT_GRAY;
|
||||||
|
self.deemphasized_text_color = Color32::DARK_GRAY;
|
||||||
|
self.highlight_color = Color32::WHITE;
|
||||||
|
self.replace_color = Color32::LIGHT_BLUE;
|
||||||
|
self.insert_color = Color32::GREEN;
|
||||||
|
self.delete_color = Color32::from_rgb(200, 40, 41);
|
||||||
|
}
|
||||||
|
eframe::Theme::Light => {
|
||||||
|
style.visuals = egui::Visuals::light();
|
||||||
|
self.text_color = Color32::GRAY;
|
||||||
|
self.emphasized_text_color = Color32::DARK_GRAY;
|
||||||
|
self.deemphasized_text_color = Color32::LIGHT_GRAY;
|
||||||
|
self.highlight_color = Color32::BLACK;
|
||||||
|
self.replace_color = Color32::DARK_BLUE;
|
||||||
|
self.insert_color = Color32::DARK_GREEN;
|
||||||
|
self.delete_color = Color32::from_rgb(200, 40, 41);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
style
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const DEFAULT_COLOR_ROTATION: [Color32; 9] = [
|
||||||
|
Color32::from_rgb(255, 0, 255),
|
||||||
|
Color32::from_rgb(0, 255, 255),
|
||||||
|
Color32::from_rgb(0, 128, 0),
|
||||||
|
Color32::from_rgb(255, 0, 0),
|
||||||
|
Color32::from_rgb(255, 255, 0),
|
||||||
|
Color32::from_rgb(255, 192, 203),
|
||||||
|
Color32::from_rgb(0, 0, 255),
|
||||||
|
Color32::from_rgb(0, 255, 0),
|
||||||
|
Color32::from_rgb(213, 138, 138),
|
||||||
|
];
|
||||||
|
|
||||||
|
pub fn appearance_window(ctx: &egui::Context, show: &mut bool, appearance: &mut Appearance) {
|
||||||
|
egui::Window::new("Appearance").open(show).show(ctx, |ui| {
|
||||||
|
egui::ComboBox::from_label("Theme")
|
||||||
|
.selected_text(format!("{:?}", appearance.theme))
|
||||||
|
.show_ui(ui, |ui| {
|
||||||
|
ui.selectable_value(&mut appearance.theme, eframe::Theme::Dark, "Dark");
|
||||||
|
ui.selectable_value(&mut appearance.theme, eframe::Theme::Light, "Light");
|
||||||
|
});
|
||||||
|
ui.label("UI font:");
|
||||||
|
egui::introspection::font_id_ui(ui, &mut appearance.ui_font);
|
||||||
|
ui.separator();
|
||||||
|
ui.label("Code font:");
|
||||||
|
egui::introspection::font_id_ui(ui, &mut appearance.code_font);
|
||||||
|
ui.separator();
|
||||||
|
ui.label("Diff colors:");
|
||||||
|
if ui.button("Reset").clicked() {
|
||||||
|
appearance.diff_colors = DEFAULT_COLOR_ROTATION.to_vec();
|
||||||
|
}
|
||||||
|
let mut remove_at: Option<usize> = None;
|
||||||
|
let num_colors = appearance.diff_colors.len();
|
||||||
|
for (idx, color) in appearance.diff_colors.iter_mut().enumerate() {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.color_edit_button_srgba(color);
|
||||||
|
if num_colors > 1 && ui.small_button("-").clicked() {
|
||||||
|
remove_at = Some(idx);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if let Some(idx) = remove_at {
|
||||||
|
appearance.diff_colors.remove(idx);
|
||||||
|
}
|
||||||
|
if ui.small_button("+").clicked() {
|
||||||
|
appearance.diff_colors.push(Color32::BLACK);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
+547
-124
File diff suppressed because it is too large
Load Diff
+39
-36
@@ -1,14 +1,16 @@
|
|||||||
use std::{cmp::min, default::Default, mem::take};
|
use std::{cmp::min, default::Default, mem::take};
|
||||||
|
|
||||||
use egui::{text::LayoutJob, Align, Color32, Label, Layout, Sense, Vec2};
|
use egui::{text::LayoutJob, Align, Label, Layout, Sense, Vec2};
|
||||||
use egui_extras::{Column, TableBuilder};
|
use egui_extras::{Column, TableBuilder};
|
||||||
use time::format_description;
|
use time::format_description;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::{SymbolReference, View, ViewConfig, ViewState},
|
|
||||||
jobs::Job,
|
|
||||||
obj::{ObjDataDiff, ObjDataDiffKind, ObjInfo, ObjSection},
|
obj::{ObjDataDiff, ObjDataDiffKind, ObjInfo, ObjSection},
|
||||||
views::{write_text, COLOR_RED},
|
views::{
|
||||||
|
appearance::Appearance,
|
||||||
|
symbol_diff::{DiffViewState, SymbolReference, View},
|
||||||
|
write_text,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const BYTES_PER_ROW: usize = 16;
|
const BYTES_PER_ROW: usize = 16;
|
||||||
@@ -17,29 +19,29 @@ fn find_section<'a>(obj: &'a ObjInfo, selected_symbol: &SymbolReference) -> Opti
|
|||||||
obj.sections.iter().find(|section| section.name == selected_symbol.section_name)
|
obj.sections.iter().find(|section| section.name == selected_symbol.section_name)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data_row_ui(ui: &mut egui::Ui, address: usize, diffs: &[ObjDataDiff], config: &ViewConfig) {
|
fn data_row_ui(ui: &mut egui::Ui, address: usize, diffs: &[ObjDataDiff], appearance: &Appearance) {
|
||||||
if diffs.iter().any(|d| d.kind != ObjDataDiffKind::None) {
|
if diffs.iter().any(|d| d.kind != ObjDataDiffKind::None) {
|
||||||
ui.painter().rect_filled(ui.available_rect_before_wrap(), 0.0, ui.visuals().faint_bg_color);
|
ui.painter().rect_filled(ui.available_rect_before_wrap(), 0.0, ui.visuals().faint_bg_color);
|
||||||
}
|
}
|
||||||
let mut job = LayoutJob::default();
|
let mut job = LayoutJob::default();
|
||||||
write_text(
|
write_text(
|
||||||
format!("{address:08X}: ").as_str(),
|
format!("{address:08X}: ").as_str(),
|
||||||
Color32::GRAY,
|
appearance.text_color,
|
||||||
&mut job,
|
&mut job,
|
||||||
config.code_font.clone(),
|
appearance.code_font.clone(),
|
||||||
);
|
);
|
||||||
let mut cur_addr = 0usize;
|
let mut cur_addr = 0usize;
|
||||||
for diff in diffs {
|
for diff in diffs {
|
||||||
let base_color = match diff.kind {
|
let base_color = match diff.kind {
|
||||||
ObjDataDiffKind::None => Color32::GRAY,
|
ObjDataDiffKind::None => appearance.text_color,
|
||||||
ObjDataDiffKind::Replace => Color32::LIGHT_BLUE,
|
ObjDataDiffKind::Replace => appearance.replace_color,
|
||||||
ObjDataDiffKind::Delete => COLOR_RED,
|
ObjDataDiffKind::Delete => appearance.delete_color,
|
||||||
ObjDataDiffKind::Insert => Color32::GREEN,
|
ObjDataDiffKind::Insert => appearance.insert_color,
|
||||||
};
|
};
|
||||||
if diff.data.is_empty() {
|
if diff.data.is_empty() {
|
||||||
let mut str = " ".repeat(diff.len);
|
let mut str = " ".repeat(diff.len);
|
||||||
str.push_str(" ".repeat(diff.len / 8).as_str());
|
str.push_str(" ".repeat(diff.len / 8).as_str());
|
||||||
write_text(str.as_str(), base_color, &mut job, config.code_font.clone());
|
write_text(str.as_str(), base_color, &mut job, appearance.code_font.clone());
|
||||||
cur_addr += diff.len;
|
cur_addr += diff.len;
|
||||||
} else {
|
} else {
|
||||||
let mut text = String::new();
|
let mut text = String::new();
|
||||||
@@ -50,7 +52,7 @@ fn data_row_ui(ui: &mut egui::Ui, address: usize, diffs: &[ObjDataDiff], config:
|
|||||||
text.push(' ');
|
text.push(' ');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
write_text(text.as_str(), base_color, &mut job, config.code_font.clone());
|
write_text(text.as_str(), base_color, &mut job, appearance.code_font.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if cur_addr < BYTES_PER_ROW {
|
if cur_addr < BYTES_PER_ROW {
|
||||||
@@ -58,22 +60,22 @@ fn data_row_ui(ui: &mut egui::Ui, address: usize, diffs: &[ObjDataDiff], config:
|
|||||||
let mut str = " ".to_string();
|
let mut str = " ".to_string();
|
||||||
str.push_str(" ".repeat(n).as_str());
|
str.push_str(" ".repeat(n).as_str());
|
||||||
str.push_str(" ".repeat(n / 8).as_str());
|
str.push_str(" ".repeat(n / 8).as_str());
|
||||||
write_text(str.as_str(), Color32::GRAY, &mut job, config.code_font.clone());
|
write_text(str.as_str(), appearance.text_color, &mut job, appearance.code_font.clone());
|
||||||
}
|
}
|
||||||
write_text(" ", Color32::GRAY, &mut job, config.code_font.clone());
|
write_text(" ", appearance.text_color, &mut job, appearance.code_font.clone());
|
||||||
for diff in diffs {
|
for diff in diffs {
|
||||||
let base_color = match diff.kind {
|
let base_color = match diff.kind {
|
||||||
ObjDataDiffKind::None => Color32::GRAY,
|
ObjDataDiffKind::None => appearance.text_color,
|
||||||
ObjDataDiffKind::Replace => Color32::LIGHT_BLUE,
|
ObjDataDiffKind::Replace => appearance.replace_color,
|
||||||
ObjDataDiffKind::Delete => COLOR_RED,
|
ObjDataDiffKind::Delete => appearance.delete_color,
|
||||||
ObjDataDiffKind::Insert => Color32::GREEN,
|
ObjDataDiffKind::Insert => appearance.insert_color,
|
||||||
};
|
};
|
||||||
if diff.data.is_empty() {
|
if diff.data.is_empty() {
|
||||||
write_text(
|
write_text(
|
||||||
" ".repeat(diff.len).as_str(),
|
" ".repeat(diff.len).as_str(),
|
||||||
base_color,
|
base_color,
|
||||||
&mut job,
|
&mut job,
|
||||||
config.code_font.clone(),
|
appearance.code_font.clone(),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
let mut text = String::new();
|
let mut text = String::new();
|
||||||
@@ -85,7 +87,7 @@ fn data_row_ui(ui: &mut egui::Ui, address: usize, diffs: &[ObjDataDiff], config:
|
|||||||
text.push('.');
|
text.push('.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
write_text(text.as_str(), base_color, &mut job, config.code_font.clone());
|
write_text(text.as_str(), base_color, &mut job, appearance.code_font.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ui.add(Label::new(job).sense(Sense::click()));
|
ui.add(Label::new(job).sense(Sense::click()));
|
||||||
@@ -133,7 +135,7 @@ fn data_table_ui(
|
|||||||
left_obj: &ObjInfo,
|
left_obj: &ObjInfo,
|
||||||
right_obj: &ObjInfo,
|
right_obj: &ObjInfo,
|
||||||
selected_symbol: &SymbolReference,
|
selected_symbol: &SymbolReference,
|
||||||
config: &ViewConfig,
|
config: &Appearance,
|
||||||
) -> Option<()> {
|
) -> Option<()> {
|
||||||
let left_section = find_section(left_obj, selected_symbol)?;
|
let left_section = find_section(left_obj, selected_symbol)?;
|
||||||
let right_section = find_section(right_obj, selected_symbol)?;
|
let right_section = find_section(right_obj, selected_symbol)?;
|
||||||
@@ -161,10 +163,10 @@ fn data_table_ui(
|
|||||||
Some(())
|
Some(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn data_diff_ui(ui: &mut egui::Ui, view_state: &mut ViewState) -> bool {
|
pub fn data_diff_ui(ui: &mut egui::Ui, state: &mut DiffViewState, appearance: &Appearance) {
|
||||||
let mut rebuild = false;
|
let (Some(result), Some(selected_symbol)) = (&state.build, &state.symbol_state.selected_symbol)
|
||||||
let (Some(result), Some(selected_symbol)) = (&view_state.build, &view_state.selected_symbol) else {
|
else {
|
||||||
return rebuild;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Header
|
// Header
|
||||||
@@ -182,13 +184,13 @@ pub fn data_diff_ui(ui: &mut egui::Ui, view_state: &mut ViewState) -> bool {
|
|||||||
ui.set_width(column_width);
|
ui.set_width(column_width);
|
||||||
|
|
||||||
if ui.button("Back").clicked() {
|
if ui.button("Back").clicked() {
|
||||||
view_state.current_view = View::SymbolDiff;
|
state.current_view = View::SymbolDiff;
|
||||||
}
|
}
|
||||||
|
|
||||||
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 = Some(false);
|
ui.style_mut().wrap = Some(false);
|
||||||
ui.colored_label(Color32::WHITE, &selected_symbol.symbol_name);
|
ui.colored_label(appearance.highlight_color, &selected_symbol.symbol_name);
|
||||||
ui.label("Diff target:");
|
ui.label("Diff target:");
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -202,14 +204,17 @@ pub fn data_diff_ui(ui: &mut egui::Ui, view_state: &mut ViewState) -> bool {
|
|||||||
ui.set_width(column_width);
|
ui.set_width(column_width);
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
if ui.button("Build").clicked() {
|
if ui
|
||||||
rebuild = true;
|
.add_enabled(!state.build_running, egui::Button::new("Build"))
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
state.queue_build = 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 = Some(false);
|
ui.style_mut().wrap = Some(false);
|
||||||
if view_state.jobs.iter().any(|job| job.job_type == Job::ObjDiff) {
|
if state.build_running {
|
||||||
ui.label("Building...");
|
ui.colored_label(appearance.replace_color, "Building…");
|
||||||
} else {
|
} else {
|
||||||
ui.label("Last built:");
|
ui.label("Last built:");
|
||||||
let format =
|
let format =
|
||||||
@@ -217,7 +222,7 @@ pub fn data_diff_ui(ui: &mut egui::Ui, view_state: &mut ViewState) -> bool {
|
|||||||
ui.label(
|
ui.label(
|
||||||
result
|
result
|
||||||
.time
|
.time
|
||||||
.to_offset(view_state.utc_offset)
|
.to_offset(appearance.utc_offset)
|
||||||
.format(&format)
|
.format(&format)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
);
|
);
|
||||||
@@ -247,8 +252,6 @@ pub fn data_diff_ui(ui: &mut egui::Ui, view_state: &mut ViewState) -> bool {
|
|||||||
.resizable(false)
|
.resizable(false)
|
||||||
.auto_shrink([false, false])
|
.auto_shrink([false, false])
|
||||||
.min_scrolled_height(available_height);
|
.min_scrolled_height(available_height);
|
||||||
data_table_ui(table, left_obj, right_obj, selected_symbol, &view_state.view_config);
|
data_table_ui(table, left_obj, right_obj, selected_symbol, appearance);
|
||||||
}
|
}
|
||||||
|
|
||||||
rebuild
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
use egui::TextStyle;
|
||||||
|
|
||||||
|
use crate::views::appearance::Appearance;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct DemangleViewState {
|
||||||
|
pub text: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn demangle_window(
|
||||||
|
ctx: &egui::Context,
|
||||||
|
show: &mut bool,
|
||||||
|
state: &mut DemangleViewState,
|
||||||
|
appearance: &Appearance,
|
||||||
|
) {
|
||||||
|
egui::Window::new("Demangle").open(show).show(ctx, |ui| {
|
||||||
|
ui.text_edit_singleline(&mut state.text);
|
||||||
|
ui.add_space(10.0);
|
||||||
|
if let Some(demangled) = cwdemangle::demangle(&state.text, &Default::default()) {
|
||||||
|
ui.scope(|ui| {
|
||||||
|
ui.style_mut().override_text_style = Some(TextStyle::Monospace);
|
||||||
|
ui.colored_label(appearance.replace_color, &demangled);
|
||||||
|
});
|
||||||
|
if ui.button("Copy").clicked() {
|
||||||
|
ui.output_mut(|output| output.copied_text = demangled);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ui.scope(|ui| {
|
||||||
|
ui.style_mut().override_text_style = Some(TextStyle::Monospace);
|
||||||
|
ui.colored_label(appearance.replace_color, "[invalid]");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user