Skip to content

Commit 3970bc8

Browse files
committed
Document configuration file & more cleanup
1 parent eaf0fab commit 3970bc8

16 files changed

+388
-231
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "objdiff"
3-
version = "0.3.4"
3+
version = "0.4.0"
44
edition = "2021"
55
rust-version = "1.65"
66
authors = ["Luke Street <luke@street.dev>"]

README.md

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,87 @@
55

66
A local diffing tool for decompilation projects.
77

8-
Currently supports:
8+
Supports:
99
- PowerPC 750CL (GameCube & Wii)
1010
- MIPS (Nintendo 64)
1111

12+
See [Usage](#usage) for more information.
13+
1214
![Symbol Screenshot](assets/screen-symbols.png)
1315
![Diff Screenshot](assets/screen-diff.png)
1416

15-
### License
17+
## Usage
18+
19+
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.
20+
21+
For example, if the target ("expected") object is located at `build/asm/MetroTRK/mslsupp.o` and the base ("actual") object
22+
is located at `build/src/MetroTRK/mslsupp.o`, the following configuration would be used:
23+
24+
- Target build directory: `build/asm`
25+
- Base build directory: `build/src`
26+
- Object: `MetroTRK/mslsupp.o`
27+
28+
objdiff will then execute the build system from the project directory to build both objects:
29+
30+
```sh
31+
$ make build/asm/MetroTRK/mslsupp.o # Only if "Build target object" is enabled
32+
$ make build/src/MetroTRK/mslsupp.o
33+
```
34+
35+
The objects will then be compared and the results will be displayed in the UI.
36+
37+
See [Configuration](#configuration) for more information.
38+
39+
## Configuration
40+
41+
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.
42+
43+
```json5
44+
// objdiff.json
45+
{
46+
"custom_make": "ninja",
47+
"target_dir": "build/mp1.0/asm",
48+
"base_dir": "build/mp1.0/src",
49+
"build_target": true,
50+
"watch_patterns": [
51+
"*.c",
52+
"*.cp",
53+
"*.cpp",
54+
"*.h",
55+
"*.hpp",
56+
"*.py"
57+
],
58+
"objects": [
59+
{
60+
"path": "MetroTRK/mslsupp.o",
61+
"name": "MetroTRK/mslsupp",
62+
"reverse_fn_order": false
63+
},
64+
// ...
65+
]
66+
}
67+
```
68+
69+
- `custom_make` _(optional)_: By default, objdiff will use `make` to build the project.
70+
If the project uses a different build system (e.g. `ninja`), specify it here.
71+
- `target_dir`: Relative from the root of the project, this where the "target" or "expected" objects are located.
72+
These are the **intended result** of the match.
73+
- `base_dir`: Relative from the root of the project, this is where the "base" or "actual" objects are located.
74+
These are objects built from the **current source code**.
75+
- `build_target`: If true, objdiff will tell the build system to build the target objects before diffing (e.g. `make path/to/target.o`).
76+
This is useful if the target objects are not built by default or can change based on project configuration or edits to assembly files.
77+
Requires the build system to be configured properly.
78+
- `watch_patterns` _(optional)_: A list of glob patterns to watch for changes. ([Supported syntax](https://docs.rs/globset/latest/globset/#syntax))
79+
If any of these files change, objdiff will automatically rebuild the objects and re-compare them.
80+
- `objects` _(optional)_: If specified, objdiff will display a list of objects in the sidebar for easy navigation.
81+
- `path`: Relative path to the object from the `target_dir` and `base_dir`.
82+
- `name` _(optional)_: The name of the object in the UI. If not specified, the object's `path` will be used.
83+
- `reverse_fn_order` _(optional)_: Displays function symbols in reversed order.
84+
Used to support MWCC's `-inline deferred` option, which reverses the order of functions in the object file.
85+
86+
87+
88+
## License
1689

1790
Licensed under either of
1891

assets/screen-diff.png

-46.3 KB
Loading

assets/screen-symbols.png

-14.2 KB
Loading

src/app.rs

Lines changed: 48 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ use notify::{RecursiveMode, Watcher};
1414
use time::UtcOffset;
1515

1616
use crate::{
17-
config::{build_globset, load_project_config, ProjectUnit, ProjectUnitNode, CONFIG_FILENAMES},
18-
jobs::{
19-
check_update::start_check_update, objdiff::start_build, Job, JobQueue, JobResult, JobStatus,
17+
config::{
18+
build_globset, load_project_config, ProjectObject, ProjectObjectNode, CONFIG_FILENAMES,
2019
},
20+
jobs::{objdiff::start_build, Job, JobQueue, JobResult, JobStatus},
2121
views::{
2222
appearance::{appearance_window, Appearance},
2323
config::{config_ui, project_window, ConfigViewState},
@@ -32,11 +32,11 @@ use crate::{
3232
#[derive(Default)]
3333
pub struct ViewState {
3434
pub jobs: JobQueue,
35-
pub show_appearance_config: bool,
35+
pub config_state: ConfigViewState,
3636
pub demangle_state: DemangleViewState,
37-
pub show_demangle: bool,
3837
pub diff_state: DiffViewState,
39-
pub config_state: ConfigViewState,
38+
pub show_appearance_config: bool,
39+
pub show_demangle: bool,
4040
pub show_project_config: bool,
4141
}
4242

@@ -54,15 +54,17 @@ pub struct AppConfig {
5454
pub watch_patterns: Vec<Glob>,
5555

5656
#[serde(skip)]
57-
pub units: Vec<ProjectUnit>,
57+
pub objects: Vec<ProjectObject>,
5858
#[serde(skip)]
59-
pub unit_nodes: Vec<ProjectUnitNode>,
59+
pub object_nodes: Vec<ProjectObjectNode>,
6060
#[serde(skip)]
6161
pub watcher_change: bool,
6262
#[serde(skip)]
6363
pub config_change: bool,
6464
#[serde(skip)]
6565
pub obj_change: bool,
66+
#[serde(skip)]
67+
pub queue_build: bool,
6668
}
6769

6870
impl AppConfig {
@@ -72,36 +74,42 @@ impl AppConfig {
7274
self.base_obj_dir = None;
7375
self.obj_path = None;
7476
self.build_target = false;
75-
self.units.clear();
76-
self.unit_nodes.clear();
77+
self.objects.clear();
78+
self.object_nodes.clear();
7779
self.watcher_change = true;
7880
self.config_change = true;
7981
self.obj_change = true;
82+
self.queue_build = false;
8083
}
8184

8285
pub fn set_target_obj_dir(&mut self, path: PathBuf) {
8386
self.target_obj_dir = Some(path);
8487
self.obj_path = None;
8588
self.obj_change = true;
89+
self.queue_build = false;
8690
}
8791

8892
pub fn set_base_obj_dir(&mut self, path: PathBuf) {
8993
self.base_obj_dir = Some(path);
9094
self.obj_path = None;
9195
self.obj_change = true;
96+
self.queue_build = false;
9297
}
9398

9499
pub fn set_obj_path(&mut self, path: String) {
95100
self.obj_path = Some(path);
96101
self.obj_change = true;
102+
self.queue_build = false;
97103
}
98104
}
99105

106+
pub type AppConfigRef = Arc<RwLock<AppConfig>>;
107+
100108
#[derive(Default)]
101109
pub struct App {
102110
appearance: Appearance,
103111
view_state: ViewState,
104-
config: Arc<RwLock<AppConfig>>,
112+
config: AppConfigRef,
105113
modified: Arc<AtomicBool>,
106114
config_modified: Arc<AtomicBool>,
107115
watcher: Option<notify::RecommendedWatcher>,
@@ -135,7 +143,7 @@ impl App {
135143
config.watcher_change = true;
136144
app.modified.store(true, Ordering::Relaxed);
137145
}
138-
app.view_state.config_state.queue_update_check = config.auto_update_check;
146+
app.view_state.config_state.queue_check_update = config.auto_update_check;
139147
app.config = Arc::new(RwLock::new(config));
140148
}
141149
}
@@ -147,6 +155,7 @@ impl App {
147155
fn pre_update(&mut self) {
148156
let ViewState { jobs, diff_state, config_state, .. } = &mut self.view_state;
149157

158+
let mut results = vec![];
150159
for (job, result) in jobs.iter_finished() {
151160
match result {
152161
Ok(result) => {
@@ -157,18 +166,13 @@ impl App {
157166
log::error!("{:?}", err);
158167
}
159168
}
160-
JobResult::ObjDiff(state) => {
161-
diff_state.build = Some(state);
162-
}
163-
JobResult::CheckUpdate(state) => {
164-
config_state.check_update = Some(state);
165-
}
166169
JobResult::Update(state) => {
167170
if let Ok(mut guard) = self.relaunch_path.lock() {
168171
*guard = Some(state.exe_path);
169172
}
170173
self.should_relaunch = true;
171174
}
175+
_ => results.push(result),
172176
}
173177
}
174178
Err(err) => {
@@ -195,11 +199,19 @@ impl App {
195199
}
196200
}
197201
}
202+
jobs.results.append(&mut results);
198203
jobs.clear_finished();
204+
205+
diff_state.pre_update(jobs, &self.config);
206+
config_state.pre_update(jobs);
207+
debug_assert!(jobs.results.is_empty());
199208
}
200209

201210
fn post_update(&mut self) {
202211
let ViewState { jobs, diff_state, config_state, .. } = &mut self.view_state;
212+
config_state.post_update(jobs, &self.config);
213+
diff_state.post_update(jobs, &self.config);
214+
203215
let Ok(mut config) = self.config.write() else {
204216
return;
205217
};
@@ -244,22 +256,23 @@ impl App {
244256
}
245257
}
246258

247-
if config.obj_path.is_some()
248-
&& self.modified.swap(false, Ordering::Relaxed)
249-
&& !jobs.is_running(Job::ObjDiff)
250-
{
251-
jobs.push(start_build(self.config.clone()));
252-
}
253-
254259
if config.obj_change {
255260
*diff_state = Default::default();
256-
jobs.push(start_build(self.config.clone()));
261+
if config.obj_path.is_some() {
262+
config.queue_build = true;
263+
}
257264
config.obj_change = false;
258265
}
259266

260-
if config_state.queue_update_check {
261-
jobs.push(start_check_update());
262-
config_state.queue_update_check = false;
267+
if self.modified.swap(false, Ordering::Relaxed) {
268+
config.queue_build = true;
269+
}
270+
271+
// Don't clear `queue_build` if a build is running. A file may have been modified during
272+
// the build, so we'll start another build after the current one finishes.
273+
if config.queue_build && config.obj_path.is_some() && !jobs.is_running(Job::ObjDiff) {
274+
jobs.push(start_build(self.config.clone()));
275+
config.queue_build = false;
263276
}
264277
}
265278
}
@@ -308,26 +321,19 @@ impl eframe::App for App {
308321
});
309322
});
310323

311-
if diff_state.current_view == View::FunctionDiff
312-
&& matches!(&diff_state.build, Some(b) if b.first_status.success && b.second_status.success)
313-
{
324+
let build_success = matches!(&diff_state.build, Some(b) if b.first_status.success && b.second_status.success);
325+
if diff_state.current_view == View::FunctionDiff && build_success {
314326
egui::CentralPanel::default().show(ctx, |ui| {
315-
if function_diff_ui(ui, jobs, diff_state, appearance) {
316-
jobs.push(start_build(config.clone()));
317-
}
327+
function_diff_ui(ui, diff_state, appearance);
318328
});
319-
} else if diff_state.current_view == View::DataDiff
320-
&& matches!(&diff_state.build, Some(b) if b.first_status.success && b.second_status.success)
321-
{
329+
} else if diff_state.current_view == View::DataDiff && build_success {
322330
egui::CentralPanel::default().show(ctx, |ui| {
323-
if data_diff_ui(ui, jobs, diff_state, appearance) {
324-
jobs.push(start_build(config.clone()));
325-
}
331+
data_diff_ui(ui, diff_state, appearance);
326332
});
327333
} else {
328334
egui::SidePanel::left("side_panel").show(ctx, |ui| {
329335
egui::ScrollArea::both().show(ui, |ui| {
330-
config_ui(ui, config, jobs, show_project_config, config_state, appearance);
336+
config_ui(ui, config, show_project_config, config_state, appearance);
331337
jobs_ui(ui, jobs, appearance);
332338
});
333339
});

0 commit comments

Comments
 (0)