diff --git a/Cargo.lock b/Cargo.lock index ce49ccd..cd4d40f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1181,6 +1181,12 @@ dependencies = [ "web-sys", ] +[[package]] +name = "itoa" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" + [[package]] name = "jni" version = "0.19.0" @@ -1533,6 +1539,15 @@ dependencies = [ "syn", ] +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ + "libc", +] + [[package]] name = "objc" version = "0.2.7" @@ -1582,6 +1597,7 @@ dependencies = [ "rfd", "serde", "thiserror", + "time", "tracing-subscriber", "tracing-wasm", "winapi", @@ -2081,6 +2097,17 @@ dependencies = [ "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]] name = "tiny-skia" version = "0.7.0" diff --git a/Cargo.toml b/Cargo.toml index 3e83d18..1dec1f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ rfd = { version = "0.10.0" } # , default-features = false, features = ['xdg-port egui_extras = "0.19.0" ppc750cl = { git = "https://github.com/terorie/ppc750cl" } rabbitizer = { git = "https://github.com/encounter/rabbitizer-rs", rev = "10c279b2ef251c62885b1dcdcfe740b0db8e9956" } +time = { version = "0.3.14", features = ["formatting", "local-offset"] } [target.'cfg(windows)'.dependencies] path-slash = "0.2.0" diff --git a/README.md b/README.md index 9764fbd..8b21c16 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ A tool for decompilation projects. Currently supports: - PowerPC 750CL (GameCube & Wii) +- MIPS (Nintendo 64) **WARNING:** Very early & unstable. diff --git a/src/app.rs b/src/app.rs index 3fba8fa..a2ff50d 100644 --- a/src/app.rs +++ b/src/app.rs @@ -12,6 +12,7 @@ use std::{ use eframe::Frame; use egui::Widget; use notify::{RecursiveMode, Watcher}; +use time::{OffsetDateTime, UtcOffset}; use crate::{ jobs::{ @@ -38,7 +39,14 @@ pub enum DiffKind { WholeBinary, } -#[derive(Default, serde::Deserialize, serde::Serialize)] +#[derive(Default, Clone)] +pub struct DiffConfig { + // TODO + // pub stripped_symbols: Vec, + // pub mapped_symbols: HashMap, +} + +#[derive(serde::Deserialize, serde::Serialize)] #[serde(default)] pub struct ViewState { #[serde(skip)] @@ -53,11 +61,35 @@ pub struct ViewState { pub current_view: View, #[serde(skip)] pub show_config: bool, + #[serde(skip)] + pub diff_config: DiffConfig, + #[serde(skip)] + pub search: String, + #[serde(skip)] + pub utc_offset: UtcOffset, // Config pub diff_kind: DiffKind, 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)] #[serde(default)] pub struct AppConfig { @@ -107,7 +139,7 @@ const CONFIG_KEY: &str = "app_config"; impl App { /// 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 // `cc.egui_ctx.set_visuals` and `cc.egui_ctx.set_fonts`. @@ -120,6 +152,7 @@ impl App { config.project_dir_change = true; } app.config = Arc::new(RwLock::new(config)); + app.view_state.utc_offset = utc_offset; app } else { Self::default() @@ -149,16 +182,20 @@ impl eframe::App for App { if view_state.current_view == View::FunctionDiff && matches!(&view_state.build, Some(b) if b.first_status.success && b.second_status.success) { - egui::SidePanel::left("side_panel").show(ctx, |ui| { - if ui.button("Back").clicked() { - view_state.current_view = View::SymbolDiff; - } - ui.separator(); - jobs_ui(ui, view_state); - }); + // egui::SidePanel::left("side_panel").show(ctx, |ui| { + // if ui.button("Back").clicked() { + // view_state.current_view = View::SymbolDiff; + // } + // ui.separator(); + // jobs_ui(ui, view_state); + // }); 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 { egui::SidePanel::left("side_panel").show(ctx, |ui| { @@ -253,6 +290,7 @@ impl eframe::App for App { }, first_obj: Some(state.first_obj), second_obj: Some(state.second_obj), + time: OffsetDateTime::now_utc(), })); } } @@ -267,7 +305,10 @@ impl eframe::App for App { let mut i = 0; while i < self.view_state.jobs.len() { 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); } else { i += 1; @@ -288,20 +329,19 @@ impl eframe::App for App { } } - if let Some(build_obj) = &config.obj_path { - if self.modified.load(Ordering::Relaxed) { - if !self - .view_state - .jobs - .iter() - .any(|j| j.job_type == Job::ObjDiff && j.handle.is_some()) - { - self.view_state - .jobs - .push(queue_build(build_obj.clone(), self.config.clone())); - } - self.modified.store(false, Ordering::Relaxed); + if config.obj_path.is_some() && self.modified.load(Ordering::Relaxed) { + if !self + .view_state + .jobs + .iter() + .any(|j| j.job_type == Job::ObjDiff && j.handle.is_some()) + { + self.view_state.jobs.push(queue_build( + self.config.clone(), + self.view_state.diff_config.clone(), + )); } + self.modified.store(false, Ordering::Relaxed); } } } diff --git a/src/diff.rs b/src/diff.rs index 223b566..0ffe155 100644 --- a/src/diff.rs +++ b/src/diff.rs @@ -3,6 +3,7 @@ use std::collections::BTreeMap; use anyhow::Result; use crate::{ + app::DiffConfig, editops::{editops_find, LevEditType}, obj::{ 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) } -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 { if let Some(right_section) = find_section(right, &left_section.name) { for left_symbol in &mut left_section.symbols { diff --git a/src/jobs/bindiff.rs b/src/jobs/bindiff.rs index f6b6c96..ee8b22a 100644 --- a/src/jobs/bindiff.rs +++ b/src/jobs/bindiff.rs @@ -3,7 +3,7 @@ use std::sync::{mpsc::Receiver, Arc, RwLock}; use anyhow::{Error, Result}; use crate::{ - app::AppConfig, + app::{AppConfig, DiffConfig}, diff::diff_objs, jobs::{queue_job, update_status, Job, JobResult, JobState, Status}, obj::{elf, ObjInfo}, @@ -31,7 +31,7 @@ fn run_build( 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)?; + 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 })) diff --git a/src/jobs/objdiff.rs b/src/jobs/objdiff.rs index 183cffd..a31a866 100644 --- a/src/jobs/objdiff.rs +++ b/src/jobs/objdiff.rs @@ -6,9 +6,10 @@ use std::{ }; use anyhow::{Context, Error, Result}; +use time::OffsetDateTime; use crate::{ - app::AppConfig, + app::{AppConfig, DiffConfig}, diff::diff_objs, jobs::{queue_job, update_status, Job, JobResult, JobState, Status}, obj::{elf, ObjInfo}, @@ -23,6 +24,7 @@ pub struct ObjDiffResult { pub second_status: BuildStatus, pub first_obj: Option, pub second_obj: Option, + pub time: OffsetDateTime, } 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( status: &Status, cancel: Receiver<()>, - obj_path: String, config: Arc>, + diff_config: DiffConfig, ) -> Result> { 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 = config.project_dir.as_ref().ok_or_else(|| Error::msg("Missing project dir"))?; let mut target_path = config @@ -86,10 +89,10 @@ fn run_build( .as_ref() .ok_or_else(|| Error::msg("Missing target obj dir"))? .to_owned(); - target_path.push(&obj_path); + target_path.push(obj_path); let mut base_path = 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 .strip_prefix(project_dir) .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)?; let second_status = run_make(project_dir, base_path_rel, &config); + let time = OffsetDateTime::now_utc(); + let mut first_obj = if first_status.success { update_status(status, format!("Loading target {}", obj_path), 2, total, &cancel)?; 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) { 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)?; - 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>) -> JobState { +pub fn queue_build(config: Arc>, diff_config: DiffConfig) -> JobState { 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) }) } diff --git a/src/main.rs b/src/main.rs index 79feb36..06b9960 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,15 +1,26 @@ #![warn(clippy::all, rust_2018_idioms)] #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +use time::UtcOffset; + // When compiling natively: #[cfg(not(target_arch = "wasm32"))] fn main() { // Log to stdout (if you run with `RUST_LOG=debug`). 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(); // 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. diff --git a/src/views/config.rs b/src/views/config.rs index 7ef640f..fa17944 100644 --- a/src/views/config.rs +++ b/src/views/config.rs @@ -149,15 +149,19 @@ pub fn config_ui(ui: &mut egui::Ui, config: &Arc>, view_state: } } if let Some(new_build_obj) = new_build_obj { - *obj_path = Some(new_build_obj.clone()); - view_state.jobs.push(queue_build(new_build_obj, config.clone())); + *obj_path = Some(new_build_obj); + view_state + .jobs + .push(queue_build(config.clone(), view_state.diff_config.clone())); } } } if let Some(obj) = obj_path { ui.label(&*obj); 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())); } } diff --git a/src/views/function_diff.rs b/src/views/function_diff.rs index 5cb745f..e55063b 100644 --- a/src/views/function_diff.rs +++ b/src/views/function_diff.rs @@ -4,9 +4,11 @@ use cwdemangle::demangle; use egui::{text::LayoutJob, Color32, Label, Sense}; use egui_extras::{Size, StripBuilder, TableBuilder}; use ppc750cl::Argument; +use time::format_description; use crate::{ - app::ViewState, + app::{View, ViewState}, + jobs::Job, obj::{ ObjInfo, ObjIns, ObjInsArg, ObjInsArgDiff, ObjInsDiff, ObjInsDiffKind, ObjReloc, ObjRelocKind, ObjSymbol, @@ -305,11 +307,57 @@ fn asm_table_ui( 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) { - StripBuilder::new(ui).size(Size::exact(40.0)).size(Size::remainder()).vertical( - |mut strip| { + StripBuilder::new(ui) + .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| { builder.sizes(Size::remainder(), 2).horizontal(|mut strip| { 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); } }); - }, - ); + }); } + rebuild }