diff --git a/src/app.rs b/src/app.rs index 126489f..b4ae71a 100644 --- a/src/app.rs +++ b/src/app.rs @@ -20,16 +20,18 @@ use crate::{ Job, JobResult, JobState, }, views::{ - config::config_ui, function_diff::function_diff_ui, jobs::jobs_ui, + config::config_ui, data_diff::data_diff_ui, function_diff::function_diff_ui, jobs::jobs_ui, symbol_diff::symbol_diff_ui, }, }; +#[allow(clippy::enum_variant_names)] #[derive(Default, Eq, PartialEq)] pub enum View { #[default] SymbolDiff, FunctionDiff, + DataDiff, } #[derive(Default, Eq, PartialEq, serde::Deserialize, serde::Serialize)] @@ -197,6 +199,16 @@ impl eframe::App for App { .push(queue_build(config.clone(), view_state.diff_config.clone())); } }); + } else if view_state.current_view == View::DataDiff + && matches!(&view_state.build, Some(b) if b.first_status.success && b.second_status.success) + { + egui::CentralPanel::default().show(ctx, |ui| { + if data_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| { config_ui(ui, config, view_state); diff --git a/src/diff.rs b/src/diff.rs index 0ffe155..4e5cf40 100644 --- a/src/diff.rs +++ b/src/diff.rs @@ -1,4 +1,4 @@ -use std::collections::BTreeMap; +use std::{collections::BTreeMap, mem::take}; use anyhow::Result; @@ -6,9 +6,9 @@ use crate::{ app::DiffConfig, editops::{editops_find, LevEditType}, obj::{ - mips, ppc, ObjArchitecture, ObjInfo, ObjInsArg, ObjInsArgDiff, ObjInsBranchFrom, - ObjInsBranchTo, ObjInsDiff, ObjInsDiffKind, ObjReloc, ObjSection, ObjSectionKind, - ObjSymbol, ObjSymbolFlags, + mips, ppc, ObjArchitecture, ObjDataDiff, ObjDataDiffKind, ObjInfo, ObjInsArg, + ObjInsArgDiff, ObjInsBranchFrom, ObjInsBranchTo, ObjInsDiff, ObjInsDiffKind, ObjReloc, + ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolFlags, }, }; @@ -340,13 +340,13 @@ fn find_symbol<'a>(symbols: &'a mut [ObjSymbol], name: &str) -> Option<&'a mut O 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 { - if let Some(right_symbol) = - find_symbol(&mut right_section.symbols, &left_symbol.name) - { - left_symbol.diff_symbol = Some(right_symbol.name.clone()); - right_symbol.diff_symbol = Some(left_symbol.name.clone()); - if left_section.kind == ObjSectionKind::Code { + if left_section.kind == ObjSectionKind::Code { + for left_symbol in &mut left_section.symbols { + if let Some(right_symbol) = + find_symbol(&mut right_section.symbols, &left_symbol.name) + { + left_symbol.diff_symbol = Some(right_symbol.name.clone()); + right_symbol.diff_symbol = Some(left_symbol.name.clone()); diff_code( left.architecture, &left_section.data, @@ -358,8 +358,189 @@ pub fn diff_objs(left: &mut ObjInfo, right: &mut ObjInfo, _diff_config: &DiffCon )?; } } + } else if left_section.kind == ObjSectionKind::Data { + diff_data(left_section, right_section); } } } Ok(()) } + +fn diff_data(left: &mut ObjSection, right: &mut ObjSection) { + let edit_ops = editops_find(&left.data, &right.data); + if edit_ops.is_empty() && !left.data.is_empty() { + left.data_diff = vec![ObjDataDiff { + data: left.data.clone(), + kind: ObjDataDiffKind::None, + len: left.data.len(), + }]; + right.data_diff = vec![ObjDataDiff { + data: right.data.clone(), + kind: ObjDataDiffKind::None, + len: right.data.len(), + }]; + return; + } + + let mut left_diff = Vec::::new(); + let mut right_diff = Vec::::new(); + let mut left_cur = 0usize; + let mut right_cur = 0usize; + let mut cur_op = LevEditType::Keep; + let mut cur_left_data = Vec::::new(); + let mut cur_right_data = Vec::::new(); + for op in edit_ops { + if left_cur < op.first_start { + left_diff.push(ObjDataDiff { + data: left.data[left_cur..op.first_start].to_vec(), + kind: ObjDataDiffKind::None, + len: op.first_start - left_cur, + }); + left_cur = op.first_start; + } + if right_cur < op.second_start { + right_diff.push(ObjDataDiff { + data: right.data[right_cur..op.second_start].to_vec(), + kind: ObjDataDiffKind::None, + len: op.second_start - right_cur, + }); + right_cur = op.second_start; + } + if cur_op != op.op_type { + match cur_op { + LevEditType::Keep => {} + LevEditType::Replace => { + let left_data = take(&mut cur_left_data); + let right_data = take(&mut cur_right_data); + let left_data_len = left_data.len(); + let right_data_len = right_data.len(); + left_diff.push(ObjDataDiff { + data: left_data, + kind: ObjDataDiffKind::Replace, + len: left_data_len, + }); + right_diff.push(ObjDataDiff { + data: right_data, + kind: ObjDataDiffKind::Replace, + len: right_data_len, + }); + } + LevEditType::Insert => { + let right_data = take(&mut cur_right_data); + let right_data_len = right_data.len(); + left_diff.push(ObjDataDiff { + data: vec![], + kind: ObjDataDiffKind::Insert, + len: right_data_len, + }); + right_diff.push(ObjDataDiff { + data: right_data, + kind: ObjDataDiffKind::Insert, + len: right_data_len, + }); + } + LevEditType::Delete => { + let left_data = take(&mut cur_left_data); + let left_data_len = left_data.len(); + left_diff.push(ObjDataDiff { + data: left_data, + kind: ObjDataDiffKind::Delete, + len: left_data_len, + }); + right_diff.push(ObjDataDiff { + data: vec![], + kind: ObjDataDiffKind::Delete, + len: left_data_len, + }); + } + } + } + match op.op_type { + LevEditType::Replace => { + cur_left_data.push(left.data[left_cur]); + cur_right_data.push(right.data[right_cur]); + left_cur += 1; + right_cur += 1; + } + LevEditType::Insert => { + cur_right_data.push(right.data[right_cur]); + right_cur += 1; + } + LevEditType::Delete => { + cur_left_data.push(left.data[left_cur]); + left_cur += 1; + } + LevEditType::Keep => unreachable!(), + } + cur_op = op.op_type; + } + // if left_cur < left.data.len() { + // let len = left.data.len() - left_cur; + // left_diff.push(ObjDataDiff { + // data: left.data[left_cur..].to_vec(), + // kind: ObjDataDiffKind::Delete, + // len, + // }); + // right_diff.push(ObjDataDiff { data: vec![], kind: ObjDataDiffKind::Delete, len }); + // } else if right_cur < right.data.len() { + // let len = right.data.len() - right_cur; + // left_diff.push(ObjDataDiff { data: vec![], kind: ObjDataDiffKind::Insert, len }); + // right_diff.push(ObjDataDiff { + // data: right.data[right_cur..].to_vec(), + // kind: ObjDataDiffKind::Insert, + // len, + // }); + // } + + // TODO: merge with above + match cur_op { + LevEditType::Keep => {} + LevEditType::Replace => { + let left_data = take(&mut cur_left_data); + let right_data = take(&mut cur_right_data); + let left_data_len = left_data.len(); + let right_data_len = right_data.len(); + left_diff.push(ObjDataDiff { + data: left_data, + kind: ObjDataDiffKind::Replace, + len: left_data_len, + }); + right_diff.push(ObjDataDiff { + data: right_data, + kind: ObjDataDiffKind::Replace, + len: right_data_len, + }); + } + LevEditType::Insert => { + let right_data = take(&mut cur_right_data); + let right_data_len = right_data.len(); + left_diff.push(ObjDataDiff { + data: vec![], + kind: ObjDataDiffKind::Insert, + len: right_data_len, + }); + right_diff.push(ObjDataDiff { + data: right_data, + kind: ObjDataDiffKind::Insert, + len: right_data_len, + }); + } + LevEditType::Delete => { + let left_data = take(&mut cur_left_data); + let left_data_len = left_data.len(); + left_diff.push(ObjDataDiff { + data: left_data, + kind: ObjDataDiffKind::Delete, + len: left_data_len, + }); + right_diff.push(ObjDataDiff { + data: vec![], + kind: ObjDataDiffKind::Delete, + len: left_data_len, + }); + } + } + + left.data_diff = left_diff; + right.data_diff = right_diff; +} diff --git a/src/obj/elf.rs b/src/obj/elf.rs index 8bd0b87..be1cdfc 100644 --- a/src/obj/elf.rs +++ b/src/obj/elf.rs @@ -91,6 +91,8 @@ fn filter_sections(obj_file: &File<'_>) -> Result> { index: section.index().0, symbols: Vec::new(), relocations: Vec::new(), + data_diff: vec![], + match_percent: 0.0, }); } result.sort_by(|a, b| a.name.cmp(&b.name)); diff --git a/src/obj/mod.rs b/src/obj/mod.rs index 89521a2..97f2ab0 100644 --- a/src/obj/mod.rs +++ b/src/obj/mod.rs @@ -32,6 +32,10 @@ pub struct ObjSection { pub index: usize, pub symbols: Vec, pub relocations: Vec, + + // Diff + pub data_diff: Vec, + pub match_percent: f32, } #[derive(Debug, Clone)] pub enum ObjInsArg { @@ -92,6 +96,20 @@ pub struct ObjInsDiff { /// Arg diffs pub arg_diff: Vec>, } +#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)] +pub enum ObjDataDiffKind { + #[default] + None, + Replace, + Delete, + Insert, +} +#[derive(Debug, Clone, Default)] +pub struct ObjDataDiff { + pub data: Vec, + pub kind: ObjDataDiffKind, + pub len: usize, +} #[derive(Debug, Clone)] pub struct ObjSymbol { pub name: String, diff --git a/src/views/data_diff.rs b/src/views/data_diff.rs new file mode 100644 index 0000000..d9e8919 --- /dev/null +++ b/src/views/data_diff.rs @@ -0,0 +1,242 @@ +use std::{cmp::min, default::Default, mem::take}; + +use egui::{text::LayoutJob, Color32, Label, Sense}; +use egui_extras::{Size, StripBuilder, TableBuilder}; +use time::format_description; + +use crate::{ + app::{View, ViewState}, + jobs::Job, + obj::{ObjDataDiff, ObjDataDiffKind, ObjInfo, ObjSection}, + views::{write_text, COLOR_RED, FONT_SIZE}, +}; + +const BYTES_PER_ROW: usize = 16; + +fn find_section<'a>(obj: &'a ObjInfo, section_name: &str) -> Option<&'a ObjSection> { + obj.sections.iter().find(|s| s.name == section_name) +} + +fn data_row_ui(ui: &mut egui::Ui, address: usize, diffs: &[ObjDataDiff]) { + 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); + } + let mut job = LayoutJob::default(); + write_text(format!("{:08X}: ", address).as_str(), Color32::GRAY, &mut job); + let mut cur_addr = 0usize; + for diff in diffs { + let base_color = match diff.kind { + ObjDataDiffKind::None => Color32::GRAY, + ObjDataDiffKind::Replace => Color32::LIGHT_BLUE, + ObjDataDiffKind::Delete => COLOR_RED, + ObjDataDiffKind::Insert => Color32::GREEN, + }; + if diff.data.is_empty() { + let mut str = " ".repeat(diff.len); + str.push_str(" ".repeat(diff.len / 8).as_str()); + write_text(str.as_str(), base_color, &mut job); + cur_addr += diff.len; + } else { + let mut text = String::new(); + for byte in &diff.data { + text.push_str(format!("{:02X} ", byte).as_str()); + cur_addr += 1; + if cur_addr % 8 == 0 { + text.push(' '); + } + } + write_text(text.as_str(), base_color, &mut job); + } + } + if cur_addr < BYTES_PER_ROW { + let n = BYTES_PER_ROW - cur_addr; + let mut str = " ".to_string(); + str.push_str(" ".repeat(n).as_str()); + str.push_str(" ".repeat(n / 8).as_str()); + write_text(str.as_str(), Color32::GRAY, &mut job); + } + write_text(" ", Color32::GRAY, &mut job); + for diff in diffs { + let base_color = match diff.kind { + ObjDataDiffKind::None => Color32::GRAY, + ObjDataDiffKind::Replace => Color32::LIGHT_BLUE, + ObjDataDiffKind::Delete => COLOR_RED, + ObjDataDiffKind::Insert => Color32::GREEN, + }; + if diff.data.is_empty() { + write_text(" ".repeat(diff.len).as_str(), base_color, &mut job); + } else { + let mut text = String::new(); + for byte in &diff.data { + let c = char::from(*byte); + if c.is_ascii() && !c.is_ascii_control() { + text.push(c); + } else { + text.push('.'); + } + } + write_text(text.as_str(), base_color, &mut job); + } + } + ui.add(Label::new(job).sense(Sense::click())); + // .on_hover_ui_at_pointer(|ui| ins_hover_ui(ui, ins)) + // .context_menu(|ui| ins_context_menu(ui, ins)); +} + +fn split_diffs(diffs: &[ObjDataDiff]) -> Vec> { + let mut split_diffs = Vec::>::new(); + let mut row_diffs = Vec::::new(); + let mut cur_addr = 0usize; + for diff in diffs { + let mut cur_len = 0usize; + while cur_len < diff.len { + let remaining_len = diff.len - cur_len; + let mut remaining_in_row = BYTES_PER_ROW - (cur_addr % BYTES_PER_ROW); + let len = min(remaining_len, remaining_in_row); + row_diffs.push(ObjDataDiff { + data: if diff.data.is_empty() { + Vec::new() + } else { + diff.data[cur_len..cur_len + len].to_vec() + }, + kind: diff.kind, + len, + }); + remaining_in_row -= len; + cur_len += len; + cur_addr += len; + if remaining_in_row == 0 { + split_diffs.push(take(&mut row_diffs)); + } + } + } + if !row_diffs.is_empty() { + split_diffs.push(take(&mut row_diffs)); + } + split_diffs +} + +fn data_table_ui( + table: TableBuilder<'_>, + left_obj: &ObjInfo, + right_obj: &ObjInfo, + section_name: &str, +) -> Option<()> { + let left_section = find_section(left_obj, section_name)?; + let right_section = find_section(right_obj, section_name)?; + + let total_bytes = left_section.data_diff.iter().fold(0usize, |accum, item| accum + item.len); + if total_bytes == 0 { + return None; + } + let total_rows = (total_bytes - 1) / BYTES_PER_ROW + 1; + + let left_diffs = split_diffs(&left_section.data_diff); + let right_diffs = split_diffs(&right_section.data_diff); + + table.body(|body| { + body.rows(FONT_SIZE, total_rows, |row_index, mut row| { + let address = row_index * BYTES_PER_ROW; + row.col(|ui| { + data_row_ui(ui, address, &left_diffs[row_index]); + }); + row.col(|ui| { + data_row_ui(ui, address, &right_diffs[row_index]); + }); + }); + }); + Some(()) +} + +pub fn data_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(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| { + strip.cell(|ui| { + ui.scope(|ui| { + ui.style_mut().override_text_style = + Some(egui::TextStyle::Monospace); + ui.style_mut().wrap = Some(false); + ui.colored_label(Color32::WHITE, selected_symbol); + ui.label("Diff target:"); + ui.separator(); + }); + }); + strip.cell(|ui| { + ui.scope(|ui| { + ui.style_mut().override_text_style = + Some(egui::TextStyle::Monospace); + ui.style_mut().wrap = Some(false); + ui.label(""); + ui.label("Diff base:"); + ui.separator(); + }); + }); + }); + }); + strip.cell(|ui| { + if let (Some(left_obj), Some(right_obj)) = + (&result.first_obj, &result.second_obj) + { + let table = TableBuilder::new(ui) + .striped(false) + .cell_layout(egui::Layout::left_to_right(egui::Align::Min)) + .column(Size::relative(0.5)) + .column(Size::relative(0.5)) + .resizable(false); + data_table_ui(table, left_obj, right_obj, selected_symbol); + } + }); + }); + } + rebuild +} diff --git a/src/views/mod.rs b/src/views/mod.rs index 7a1fdb2..908a905 100644 --- a/src/views/mod.rs +++ b/src/views/mod.rs @@ -1,6 +1,7 @@ use egui::{text::LayoutJob, Color32, FontFamily, FontId, TextFormat}; pub(crate) mod config; +pub(crate) mod data_diff; pub(crate) mod function_diff; pub(crate) mod jobs; pub(crate) mod symbol_diff; diff --git a/src/views/symbol_diff.rs b/src/views/symbol_diff.rs index a4ed1d8..e6725ac 100644 --- a/src/views/symbol_diff.rs +++ b/src/views/symbol_diff.rs @@ -6,7 +6,7 @@ use egui_extras::{Size, StripBuilder}; use crate::{ app::{View, ViewState}, jobs::objdiff::BuildStatus, - obj::{ObjInfo, ObjSymbol, ObjSymbolFlags}, + obj::{ObjInfo, ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolFlags}, views::write_text, }; @@ -52,6 +52,7 @@ fn symbol_hover_ui(ui: &mut Ui, symbol: &ObjSymbol) { fn symbol_ui( ui: &mut Ui, symbol: &ObjSymbol, + section: Option<&ObjSection>, highlighted_symbol: &mut Option, selected_symbol: &mut Option, current_view: &mut View, @@ -90,8 +91,15 @@ fn symbol_ui( .context_menu(|ui| symbol_context_menu_ui(ui, symbol)) .on_hover_ui_at_pointer(|ui| symbol_hover_ui(ui, symbol)); if response.clicked() { - *selected_symbol = Some(symbol.name.clone()); - *current_view = View::FunctionDiff; + if let Some(section) = section { + if section.kind == ObjSectionKind::Code { + *selected_symbol = Some(symbol.name.clone()); + *current_view = View::FunctionDiff; + } else if section.kind == ObjSectionKind::Data { + *selected_symbol = Some(section.name.clone()); + *current_view = View::DataDiff; + } + } } else if response.hovered() { *highlighted_symbol = Some(symbol.name.clone()); } @@ -127,7 +135,14 @@ fn symbol_list_ui( if !obj.common.is_empty() { CollapsingHeader::new(".comm").default_open(true).show(ui, |ui| { for symbol in &obj.common { - symbol_ui(ui, symbol, highlighted_symbol, selected_symbol, current_view); + symbol_ui( + ui, + symbol, + None, + highlighted_symbol, + selected_symbol, + current_view, + ); } }); } @@ -144,6 +159,7 @@ fn symbol_list_ui( symbol_ui( ui, symbol, + Some(section), highlighted_symbol, selected_symbol, current_view, @@ -157,6 +173,7 @@ fn symbol_list_ui( symbol_ui( ui, symbol, + Some(section), highlighted_symbol, selected_symbol, current_view,