Rework function diff view (fixes #12)

This commit is contained in:
Luke Street 2022-09-20 19:04:32 -04:00
parent 1e44f73b2f
commit 3224be21db
10 changed files with 183 additions and 45 deletions

27
Cargo.lock generated
View File

@ -1181,6 +1181,12 @@ dependencies = [
"web-sys", "web-sys",
] ]
[[package]]
name = "itoa"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754"
[[package]] [[package]]
name = "jni" name = "jni"
version = "0.19.0" version = "0.19.0"
@ -1533,6 +1539,15 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "num_threads"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "objc" name = "objc"
version = "0.2.7" version = "0.2.7"
@ -1582,6 +1597,7 @@ dependencies = [
"rfd", "rfd",
"serde", "serde",
"thiserror", "thiserror",
"time",
"tracing-subscriber", "tracing-subscriber",
"tracing-wasm", "tracing-wasm",
"winapi", "winapi",
@ -2081,6 +2097,17 @@ dependencies = [
"once_cell", "once_cell",
] ]
[[package]]
name = "time"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c3f9a28b618c3a6b9251b6908e9c99e04b9e5c02e6581ccbb67d59c34ef7f9b"
dependencies = [
"itoa",
"libc",
"num_threads",
]
[[package]] [[package]]
name = "tiny-skia" name = "tiny-skia"
version = "0.7.0" version = "0.7.0"

View File

@ -26,6 +26,7 @@ rfd = { version = "0.10.0" } # , default-features = false, features = ['xdg-port
egui_extras = "0.19.0" egui_extras = "0.19.0"
ppc750cl = { git = "https://github.com/terorie/ppc750cl" } ppc750cl = { git = "https://github.com/terorie/ppc750cl" }
rabbitizer = { git = "https://github.com/encounter/rabbitizer-rs", rev = "10c279b2ef251c62885b1dcdcfe740b0db8e9956" } rabbitizer = { git = "https://github.com/encounter/rabbitizer-rs", rev = "10c279b2ef251c62885b1dcdcfe740b0db8e9956" }
time = { version = "0.3.14", features = ["formatting", "local-offset"] }
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
path-slash = "0.2.0" path-slash = "0.2.0"

View File

@ -7,6 +7,7 @@ A tool for decompilation projects.
Currently supports: Currently supports:
- PowerPC 750CL (GameCube & Wii) - PowerPC 750CL (GameCube & Wii)
- MIPS (Nintendo 64)
**WARNING:** Very early & unstable. **WARNING:** Very early & unstable.

View File

@ -12,6 +12,7 @@ use std::{
use eframe::Frame; use eframe::Frame;
use egui::Widget; use egui::Widget;
use notify::{RecursiveMode, Watcher}; use notify::{RecursiveMode, Watcher};
use time::{OffsetDateTime, UtcOffset};
use crate::{ use crate::{
jobs::{ jobs::{
@ -38,7 +39,14 @@ pub enum DiffKind {
WholeBinary, WholeBinary,
} }
#[derive(Default, serde::Deserialize, serde::Serialize)] #[derive(Default, Clone)]
pub struct DiffConfig {
// TODO
// pub stripped_symbols: Vec<String>,
// pub mapped_symbols: HashMap<String, String>,
}
#[derive(serde::Deserialize, serde::Serialize)]
#[serde(default)] #[serde(default)]
pub struct ViewState { pub struct ViewState {
#[serde(skip)] #[serde(skip)]
@ -53,11 +61,35 @@ pub struct ViewState {
pub current_view: View, pub current_view: View,
#[serde(skip)] #[serde(skip)]
pub show_config: bool, pub show_config: bool,
#[serde(skip)]
pub diff_config: DiffConfig,
#[serde(skip)]
pub search: String,
#[serde(skip)]
pub utc_offset: UtcOffset,
// Config // Config
pub diff_kind: DiffKind, pub diff_kind: DiffKind,
pub reverse_fn_order: bool, pub reverse_fn_order: bool,
} }
impl Default for ViewState {
fn default() -> Self {
Self {
jobs: vec![],
build: None,
highlighted_symbol: None,
selected_symbol: None,
current_view: Default::default(),
show_config: false,
diff_config: Default::default(),
search: Default::default(),
utc_offset: UtcOffset::UTC,
diff_kind: Default::default(),
reverse_fn_order: false,
}
}
}
#[derive(Default, Clone, serde::Deserialize, serde::Serialize)] #[derive(Default, Clone, serde::Deserialize, serde::Serialize)]
#[serde(default)] #[serde(default)]
pub struct AppConfig { pub struct AppConfig {
@ -107,7 +139,7 @@ const CONFIG_KEY: &str = "app_config";
impl App { impl App {
/// Called once before the first frame. /// Called once before the first frame.
pub fn new(cc: &eframe::CreationContext<'_>) -> Self { pub fn new(cc: &eframe::CreationContext<'_>, utc_offset: UtcOffset) -> Self {
// This is also where you can customized the look at feel of egui using // This is also where you can customized the look at feel of egui using
// `cc.egui_ctx.set_visuals` and `cc.egui_ctx.set_fonts`. // `cc.egui_ctx.set_visuals` and `cc.egui_ctx.set_fonts`.
@ -120,6 +152,7 @@ impl App {
config.project_dir_change = true; config.project_dir_change = true;
} }
app.config = Arc::new(RwLock::new(config)); app.config = Arc::new(RwLock::new(config));
app.view_state.utc_offset = utc_offset;
app app
} else { } else {
Self::default() Self::default()
@ -149,16 +182,20 @@ impl eframe::App for App {
if view_state.current_view == View::FunctionDiff if view_state.current_view == View::FunctionDiff
&& matches!(&view_state.build, Some(b) if b.first_status.success && b.second_status.success) && matches!(&view_state.build, Some(b) if b.first_status.success && b.second_status.success)
{ {
egui::SidePanel::left("side_panel").show(ctx, |ui| { // egui::SidePanel::left("side_panel").show(ctx, |ui| {
if ui.button("Back").clicked() { // if ui.button("Back").clicked() {
view_state.current_view = View::SymbolDiff; // view_state.current_view = View::SymbolDiff;
} // }
ui.separator(); // ui.separator();
jobs_ui(ui, view_state); // jobs_ui(ui, view_state);
}); // });
egui::CentralPanel::default().show(ctx, |ui| { egui::CentralPanel::default().show(ctx, |ui| {
function_diff_ui(ui, view_state); if function_diff_ui(ui, view_state) {
view_state
.jobs
.push(queue_build(config.clone(), view_state.diff_config.clone()));
}
}); });
} else { } else {
egui::SidePanel::left("side_panel").show(ctx, |ui| { egui::SidePanel::left("side_panel").show(ctx, |ui| {
@ -253,6 +290,7 @@ impl eframe::App for App {
}, },
first_obj: Some(state.first_obj), first_obj: Some(state.first_obj),
second_obj: Some(state.second_obj), second_obj: Some(state.second_obj),
time: OffsetDateTime::now_utc(),
})); }));
} }
} }
@ -267,7 +305,10 @@ impl eframe::App for App {
let mut i = 0; let mut i = 0;
while i < self.view_state.jobs.len() { while i < self.view_state.jobs.len() {
let job = &self.view_state.jobs[i]; let job = &self.view_state.jobs[i];
if job.should_remove && job.handle.is_none() { if job.should_remove
&& job.handle.is_none()
&& job.status.read().unwrap().error.is_none()
{
self.view_state.jobs.remove(i); self.view_state.jobs.remove(i);
} else { } else {
i += 1; i += 1;
@ -288,23 +329,22 @@ impl eframe::App for App {
} }
} }
if let Some(build_obj) = &config.obj_path { if config.obj_path.is_some() && self.modified.load(Ordering::Relaxed) {
if self.modified.load(Ordering::Relaxed) {
if !self if !self
.view_state .view_state
.jobs .jobs
.iter() .iter()
.any(|j| j.job_type == Job::ObjDiff && j.handle.is_some()) .any(|j| j.job_type == Job::ObjDiff && j.handle.is_some())
{ {
self.view_state self.view_state.jobs.push(queue_build(
.jobs self.config.clone(),
.push(queue_build(build_obj.clone(), self.config.clone())); self.view_state.diff_config.clone(),
));
} }
self.modified.store(false, Ordering::Relaxed); self.modified.store(false, Ordering::Relaxed);
} }
} }
} }
}
} }
fn create_watcher( fn create_watcher(

View File

@ -3,6 +3,7 @@ use std::collections::BTreeMap;
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, ObjInfo, ObjInsArg, ObjInsArgDiff, ObjInsBranchFrom, mips, ppc, ObjArchitecture, ObjInfo, ObjInsArg, ObjInsArgDiff, ObjInsBranchFrom,
@ -336,7 +337,7 @@ fn find_symbol<'a>(symbols: &'a mut [ObjSymbol], name: &str) -> Option<&'a mut O
symbols.iter_mut().find(|s| s.name == name) symbols.iter_mut().find(|s| s.name == name)
} }
pub fn diff_objs(left: &mut ObjInfo, right: &mut ObjInfo) -> Result<()> { pub fn diff_objs(left: &mut ObjInfo, right: &mut ObjInfo, _diff_config: &DiffConfig) -> Result<()> {
for left_section in &mut left.sections { for left_section in &mut left.sections {
if let Some(right_section) = find_section(right, &left_section.name) { if let Some(right_section) = find_section(right, &left_section.name) {
for left_symbol in &mut left_section.symbols { for left_symbol in &mut left_section.symbols {

View File

@ -3,7 +3,7 @@ use std::sync::{mpsc::Receiver, Arc, RwLock};
use anyhow::{Error, Result}; use anyhow::{Error, Result};
use crate::{ use crate::{
app::AppConfig, app::{AppConfig, DiffConfig},
diff::diff_objs, diff::diff_objs,
jobs::{queue_job, update_status, Job, JobResult, JobState, Status}, jobs::{queue_job, update_status, Job, JobResult, JobState, Status},
obj::{elf, ObjInfo}, obj::{elf, ObjInfo},
@ -31,7 +31,7 @@ fn run_build(
let mut right_obj = elf::read(base_path)?; let mut right_obj = elf::read(base_path)?;
update_status(status, "Performing diff".to_string(), 2, 3, &cancel)?; update_status(status, "Performing diff".to_string(), 2, 3, &cancel)?;
diff_objs(&mut left_obj, &mut right_obj)?; diff_objs(&mut left_obj, &mut right_obj, &DiffConfig::default() /* TODO */)?;
update_status(status, "Complete".to_string(), 3, 3, &cancel)?; update_status(status, "Complete".to_string(), 3, 3, &cancel)?;
Ok(Box::new(BinDiffResult { first_obj: left_obj, second_obj: right_obj })) Ok(Box::new(BinDiffResult { first_obj: left_obj, second_obj: right_obj }))

View File

@ -6,9 +6,10 @@ use std::{
}; };
use anyhow::{Context, Error, Result}; use anyhow::{Context, Error, Result};
use time::OffsetDateTime;
use crate::{ use crate::{
app::AppConfig, app::{AppConfig, DiffConfig},
diff::diff_objs, diff::diff_objs,
jobs::{queue_job, update_status, Job, JobResult, JobState, Status}, jobs::{queue_job, update_status, Job, JobResult, JobState, Status},
obj::{elf, ObjInfo}, obj::{elf, ObjInfo},
@ -23,6 +24,7 @@ pub struct ObjDiffResult {
pub second_status: BuildStatus, pub second_status: BuildStatus,
pub first_obj: Option<ObjInfo>, pub first_obj: Option<ObjInfo>,
pub second_obj: Option<ObjInfo>, pub second_obj: Option<ObjInfo>,
pub time: OffsetDateTime,
} }
fn run_make(cwd: &Path, arg: &Path, config: &AppConfig) -> BuildStatus { fn run_make(cwd: &Path, arg: &Path, config: &AppConfig) -> BuildStatus {
@ -75,10 +77,11 @@ fn run_make(cwd: &Path, arg: &Path, config: &AppConfig) -> BuildStatus {
fn run_build( fn run_build(
status: &Status, status: &Status,
cancel: Receiver<()>, cancel: Receiver<()>,
obj_path: String,
config: Arc<RwLock<AppConfig>>, config: Arc<RwLock<AppConfig>>,
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 project_dir = let project_dir =
config.project_dir.as_ref().ok_or_else(|| Error::msg("Missing project dir"))?; config.project_dir.as_ref().ok_or_else(|| Error::msg("Missing project dir"))?;
let mut target_path = config let mut target_path = config
@ -86,10 +89,10 @@ fn run_build(
.as_ref() .as_ref()
.ok_or_else(|| Error::msg("Missing target obj dir"))? .ok_or_else(|| Error::msg("Missing target obj dir"))?
.to_owned(); .to_owned();
target_path.push(&obj_path); target_path.push(obj_path);
let mut base_path = let mut base_path =
config.base_obj_dir.as_ref().ok_or_else(|| Error::msg("Missing base obj dir"))?.to_owned(); config.base_obj_dir.as_ref().ok_or_else(|| Error::msg("Missing base obj dir"))?.to_owned();
base_path.push(&obj_path); base_path.push(obj_path);
let target_path_rel = target_path let target_path_rel = target_path
.strip_prefix(project_dir) .strip_prefix(project_dir)
.context("Failed to create relative target obj path")?; .context("Failed to create relative target obj path")?;
@ -107,6 +110,8 @@ fn run_build(
update_status(status, format!("Building base {}", obj_path), 1, total, &cancel)?; update_status(status, format!("Building base {}", obj_path), 1, total, &cancel)?;
let second_status = run_make(project_dir, base_path_rel, &config); let second_status = run_make(project_dir, base_path_rel, &config);
let time = OffsetDateTime::now_utc();
let mut first_obj = if first_status.success { let mut first_obj = if first_status.success {
update_status(status, format!("Loading target {}", obj_path), 2, total, &cancel)?; update_status(status, format!("Loading target {}", obj_path), 2, total, &cancel)?;
Some(elf::read(&target_path)?) Some(elf::read(&target_path)?)
@ -123,15 +128,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_objs(first_obj, second_obj, &diff_config)?;
} }
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 })) Ok(Box::new(ObjDiffResult { first_status, second_status, first_obj, second_obj, time }))
} }
pub fn queue_build(obj_path: String, config: Arc<RwLock<AppConfig>>) -> JobState { pub fn queue_build(config: Arc<RwLock<AppConfig>>, diff_config: DiffConfig) -> JobState {
queue_job(Job::ObjDiff, move |status, cancel| { queue_job(Job::ObjDiff, move |status, cancel| {
run_build(status, cancel, obj_path, config).map(JobResult::ObjDiff) run_build(status, cancel, config, diff_config).map(JobResult::ObjDiff)
}) })
} }

View File

@ -1,15 +1,26 @@
#![warn(clippy::all, rust_2018_idioms)] #![warn(clippy::all, rust_2018_idioms)]
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
use time::UtcOffset;
// When compiling natively: // When compiling natively:
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
fn main() { fn main() {
// Log to stdout (if you run with `RUST_LOG=debug`). // Log to stdout (if you run with `RUST_LOG=debug`).
tracing_subscriber::fmt::init(); tracing_subscriber::fmt::init();
// Because localtime_r is unsound in multithreaded apps,
// we must call this before initializing eframe.
// https://github.com/time-rs/time/issues/293
let utc_offset = UtcOffset::current_local_offset().unwrap();
let native_options = eframe::NativeOptions::default(); let native_options = eframe::NativeOptions::default();
// native_options.renderer = eframe::Renderer::Wgpu; // native_options.renderer = eframe::Renderer::Wgpu;
eframe::run_native("objdiff", native_options, Box::new(|cc| Box::new(objdiff::App::new(cc)))); eframe::run_native(
"objdiff",
native_options,
Box::new(move |cc| Box::new(objdiff::App::new(cc, utc_offset))),
);
} }
// when compiling to web using trunk. // when compiling to web using trunk.

View File

@ -149,15 +149,19 @@ pub fn config_ui(ui: &mut egui::Ui, config: &Arc<RwLock<AppConfig>>, view_state:
} }
} }
if let Some(new_build_obj) = new_build_obj { if let Some(new_build_obj) = new_build_obj {
*obj_path = Some(new_build_obj.clone()); *obj_path = Some(new_build_obj);
view_state.jobs.push(queue_build(new_build_obj, config.clone())); view_state
.jobs
.push(queue_build(config.clone(), view_state.diff_config.clone()));
} }
} }
} }
if let Some(obj) = obj_path { if let Some(obj) = obj_path {
ui.label(&*obj); ui.label(&*obj);
if ui.button("Build").clicked() { if ui.button("Build").clicked() {
view_state.jobs.push(queue_build(obj.clone(), config.clone())); view_state
.jobs
.push(queue_build(config.clone(), view_state.diff_config.clone()));
} }
} }

View File

@ -4,9 +4,11 @@ use cwdemangle::demangle;
use egui::{text::LayoutJob, Color32, Label, Sense}; use egui::{text::LayoutJob, Color32, Label, Sense};
use egui_extras::{Size, StripBuilder, TableBuilder}; use egui_extras::{Size, StripBuilder, TableBuilder};
use ppc750cl::Argument; use ppc750cl::Argument;
use time::format_description;
use crate::{ use crate::{
app::ViewState, app::{View, ViewState},
jobs::Job,
obj::{ obj::{
ObjInfo, ObjIns, ObjInsArg, ObjInsArgDiff, ObjInsDiff, ObjInsDiffKind, ObjReloc, ObjInfo, ObjIns, ObjInsArg, ObjInsArgDiff, ObjInsDiff, ObjInsDiffKind, ObjReloc,
ObjRelocKind, ObjSymbol, ObjRelocKind, ObjSymbol,
@ -305,11 +307,57 @@ fn asm_table_ui(
Some(()) Some(())
} }
pub fn function_diff_ui(ui: &mut egui::Ui, view_state: &mut ViewState) { pub fn function_diff_ui(ui: &mut egui::Ui, view_state: &mut ViewState) -> bool {
let mut rebuild = false;
if let (Some(result), Some(selected_symbol)) = (&view_state.build, &view_state.selected_symbol) if let (Some(result), Some(selected_symbol)) = (&view_state.build, &view_state.selected_symbol)
{ {
StripBuilder::new(ui).size(Size::exact(40.0)).size(Size::remainder()).vertical( StripBuilder::new(ui)
|mut strip| { .size(Size::exact(20.0))
.size(Size::exact(40.0))
.size(Size::remainder())
.vertical(|mut strip| {
strip.strip(|builder| {
builder.sizes(Size::remainder(), 2).horizontal(|mut strip| {
strip.cell(|ui| {
ui.horizontal(|ui| {
if ui.button("Back").clicked() {
view_state.current_view = View::SymbolDiff;
}
});
});
strip.cell(|ui| {
ui.horizontal(|ui| {
if ui.button("Build").clicked() {
rebuild = true;
}
ui.scope(|ui| {
ui.style_mut().override_text_style =
Some(egui::TextStyle::Monospace);
ui.style_mut().wrap = Some(false);
if view_state
.jobs
.iter()
.any(|job| job.job_type == Job::ObjDiff)
{
ui.label("Building...");
} else {
ui.label("Last built:");
let format =
format_description::parse("[hour]:[minute]:[second]")
.unwrap();
ui.label(
result
.time
.to_offset(view_state.utc_offset)
.format(&format)
.unwrap(),
);
}
});
});
});
});
});
strip.strip(|builder| { strip.strip(|builder| {
builder.sizes(Size::remainder(), 2).horizontal(|mut strip| { builder.sizes(Size::remainder(), 2).horizontal(|mut strip| {
let demangled = demangle(selected_symbol); let demangled = demangle(selected_symbol);
@ -359,7 +407,7 @@ pub fn function_diff_ui(ui: &mut egui::Ui, view_state: &mut ViewState) {
asm_table_ui(table, left_obj, right_obj, selected_symbol); asm_table_ui(table, left_obj, right_obj, selected_symbol);
} }
}); });
}, });
);
} }
rebuild
} }