mirror of
https://github.com/encounter/objdiff.git
synced 2026-07-10 12:18:36 -07:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7834185223 | |||
| 216dd70d5b | |||
| 976e629f08 |
Generated
+4
-4
@@ -3491,7 +3491,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "objdiff-cli"
|
||||
version = "3.5.0"
|
||||
version = "3.5.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argp",
|
||||
@@ -3514,7 +3514,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "objdiff-core"
|
||||
version = "3.5.0"
|
||||
version = "3.5.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arm-attr",
|
||||
@@ -3570,7 +3570,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "objdiff-gui"
|
||||
version = "3.5.0"
|
||||
version = "3.5.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argp",
|
||||
@@ -3608,7 +3608,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "objdiff-wasm"
|
||||
version = "3.5.0"
|
||||
version = "3.5.1"
|
||||
dependencies = [
|
||||
"log",
|
||||
"objdiff-core",
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ default-members = [
|
||||
resolver = "3"
|
||||
|
||||
[workspace.package]
|
||||
version = "3.5.0"
|
||||
version = "3.5.1"
|
||||
authors = ["Luke Street <luke@street.dev>"]
|
||||
edition = "2024"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
@@ -83,7 +83,7 @@ impl fmt::Display for DataType {
|
||||
impl DataType {
|
||||
pub fn display_labels(&self, endian: object::Endianness, bytes: &[u8]) -> Vec<String> {
|
||||
let mut strs = Vec::new();
|
||||
for (literal, label_override) in self.display_literals(endian, bytes) {
|
||||
for (literal, label_override, _) in self.display_literals(endian, bytes) {
|
||||
let label = label_override.unwrap_or_else(|| self.to_string());
|
||||
strs.push(format!("{label}: {literal:?}"))
|
||||
}
|
||||
@@ -94,7 +94,7 @@ impl DataType {
|
||||
&self,
|
||||
endian: object::Endianness,
|
||||
bytes: &[u8],
|
||||
) -> Vec<(String, Option<String>)> {
|
||||
) -> Vec<(String, Option<String>, Option<String>)> {
|
||||
let mut strs = Vec::new();
|
||||
if self.required_len().is_some_and(|l| bytes.len() < l) {
|
||||
log::warn!(
|
||||
@@ -118,34 +118,34 @@ impl DataType {
|
||||
match self {
|
||||
DataType::Int8 => {
|
||||
let i = i8::from_ne_bytes(bytes.try_into().unwrap());
|
||||
strs.push((format!("{i:#x}"), None));
|
||||
strs.push((format!("{i:#x}"), None, None));
|
||||
|
||||
if i < 0 {
|
||||
strs.push((format!("{:#x}", ReallySigned(i)), None));
|
||||
strs.push((format!("{:#x}", ReallySigned(i)), None, None));
|
||||
}
|
||||
}
|
||||
DataType::Int16 => {
|
||||
let i = endian.read_i16_bytes(bytes.try_into().unwrap());
|
||||
strs.push((format!("{i:#x}"), None));
|
||||
strs.push((format!("{i:#x}"), None, None));
|
||||
|
||||
if i < 0 {
|
||||
strs.push((format!("{:#x}", ReallySigned(i)), None));
|
||||
strs.push((format!("{:#x}", ReallySigned(i)), None, None));
|
||||
}
|
||||
}
|
||||
DataType::Int32 => {
|
||||
let i = endian.read_i32_bytes(bytes.try_into().unwrap());
|
||||
strs.push((format!("{i:#x}"), None));
|
||||
strs.push((format!("{i:#x}"), None, None));
|
||||
|
||||
if i < 0 {
|
||||
strs.push((format!("{:#x}", ReallySigned(i)), None));
|
||||
strs.push((format!("{:#x}", ReallySigned(i)), None, None));
|
||||
}
|
||||
}
|
||||
DataType::Int64 => {
|
||||
let i = endian.read_i64_bytes(bytes.try_into().unwrap());
|
||||
strs.push((format!("{i:#x}"), None));
|
||||
strs.push((format!("{i:#x}"), None, None));
|
||||
|
||||
if i < 0 {
|
||||
strs.push((format!("{:#x}", ReallySigned(i)), None));
|
||||
strs.push((format!("{:#x}", ReallySigned(i)), None, None));
|
||||
}
|
||||
}
|
||||
DataType::Float => {
|
||||
@@ -156,6 +156,7 @@ impl DataType {
|
||||
object::Endianness::Big => f32::from_be_bytes(bytes),
|
||||
}),
|
||||
None,
|
||||
None,
|
||||
));
|
||||
}
|
||||
DataType::Double => {
|
||||
@@ -166,10 +167,11 @@ impl DataType {
|
||||
object::Endianness::Big => f64::from_be_bytes(bytes),
|
||||
}),
|
||||
None,
|
||||
None,
|
||||
));
|
||||
}
|
||||
DataType::Bytes => {
|
||||
strs.push((format!("{bytes:#?}"), None));
|
||||
strs.push((format!("{bytes:#?}"), None, None));
|
||||
}
|
||||
DataType::String => {
|
||||
if let Some(nul_idx) = bytes.iter().position(|&c| c == b'\0') {
|
||||
@@ -177,13 +179,17 @@ impl DataType {
|
||||
// Special case to display (ASCII) as the label for ASCII-only strings.
|
||||
let (cow, _, had_errors) = encoding_rs::UTF_8.decode(str_bytes);
|
||||
if !had_errors && cow.is_ascii() {
|
||||
strs.push((format!("{cow}"), Some("ASCII".into())));
|
||||
let string = format!("{cow}");
|
||||
let copy_string = escape_special_ascii_characters(string.clone());
|
||||
strs.push((string, Some("ASCII".into()), Some(copy_string)));
|
||||
}
|
||||
for (encoding, encoding_name) in SUPPORTED_ENCODINGS {
|
||||
let (cow, _, had_errors) = encoding.decode(str_bytes);
|
||||
// Avoid showing ASCII-only strings more than once if the encoding is ASCII-compatible.
|
||||
if !had_errors && (!encoding.is_ascii_compatible() || !cow.is_ascii()) {
|
||||
strs.push((format!("{cow}"), Some(encoding_name.into())));
|
||||
let string = format!("{cow}");
|
||||
let copy_string = escape_special_ascii_characters(string.clone());
|
||||
strs.push((string, Some(encoding_name.into()), Some(copy_string)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -499,3 +505,21 @@ pub struct RelocationOverride {
|
||||
pub target: RelocationOverrideTarget,
|
||||
pub addend: i64,
|
||||
}
|
||||
|
||||
/// Escape ASCII characters such as \n or \t, but not Unicode characters such as \u{3000}.
|
||||
/// Suitable for copying to clipboard.
|
||||
fn escape_special_ascii_characters(value: String) -> String {
|
||||
let mut escaped = String::new();
|
||||
escaped.push('"');
|
||||
for c in value.chars() {
|
||||
if c.is_ascii() {
|
||||
for e in c.escape_default() {
|
||||
escaped.push(e);
|
||||
}
|
||||
} else {
|
||||
escaped.push(c);
|
||||
}
|
||||
}
|
||||
escaped.push('"');
|
||||
escaped
|
||||
}
|
||||
|
||||
@@ -440,9 +440,13 @@ impl Arch for ArchPpc {
|
||||
let simplified = ins.simplified().to_string();
|
||||
let show_orig = orig != simplified;
|
||||
let mut out = Vec::with_capacity(2);
|
||||
out.push(ContextItem::Copy { value: simplified, label: None });
|
||||
out.push(ContextItem::Copy { value: simplified, label: None, copy_string: None });
|
||||
if show_orig {
|
||||
out.push(ContextItem::Copy { value: orig, label: Some("original".to_string()) });
|
||||
out.push(ContextItem::Copy {
|
||||
value: orig,
|
||||
label: Some("original".to_string()),
|
||||
copy_string: None,
|
||||
});
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
@@ -366,7 +366,7 @@ impl From<&DiffText<'_>> for HighlightKind {
|
||||
}
|
||||
|
||||
pub enum ContextItem {
|
||||
Copy { value: String, label: Option<String> },
|
||||
Copy { value: String, label: Option<String>, copy_string: Option<String> },
|
||||
Navigate { label: String, symbol_index: usize, kind: SymbolNavigationKind },
|
||||
Separator,
|
||||
}
|
||||
@@ -398,9 +398,9 @@ pub fn symbol_context(obj: &Object, symbol_index: usize) -> Vec<ContextItem> {
|
||||
return Vec::new();
|
||||
};
|
||||
let mut out = Vec::new();
|
||||
out.push(ContextItem::Copy { value: symbol.name.clone(), label: None });
|
||||
out.push(ContextItem::Copy { value: symbol.name.clone(), label: None, copy_string: None });
|
||||
if let Some(name) = &symbol.demangled_name {
|
||||
out.push(ContextItem::Copy { value: name.clone(), label: None });
|
||||
out.push(ContextItem::Copy { value: name.clone(), label: None, copy_string: None });
|
||||
}
|
||||
if symbol.section.is_some()
|
||||
&& let Some(address) = symbol.virtual_address
|
||||
@@ -408,6 +408,7 @@ pub fn symbol_context(obj: &Object, symbol_index: usize) -> Vec<ContextItem> {
|
||||
out.push(ContextItem::Copy {
|
||||
value: format!("{address:x}"),
|
||||
label: Some("virtual address".to_string()),
|
||||
copy_string: None,
|
||||
});
|
||||
}
|
||||
out.append(&mut obj.arch.symbol_context(obj, symbol_index));
|
||||
@@ -501,8 +502,8 @@ pub fn relocation_context(
|
||||
let literals = display_ins_data_literals(obj, ins);
|
||||
if !literals.is_empty() {
|
||||
out.push(ContextItem::Separator);
|
||||
for (literal, label_override) in literals {
|
||||
out.push(ContextItem::Copy { value: literal, label: label_override });
|
||||
for (literal, label_override, copy_string) in literals {
|
||||
out.push(ContextItem::Copy { value: literal, label: label_override, copy_string });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -598,24 +599,37 @@ pub fn instruction_context(
|
||||
for byte in resolved.code {
|
||||
hex_string.push_str(&format!("{byte:02x}"));
|
||||
}
|
||||
out.push(ContextItem::Copy { value: hex_string, label: Some("instruction bytes".to_string()) });
|
||||
out.push(ContextItem::Copy {
|
||||
value: hex_string,
|
||||
label: Some("instruction bytes".to_string()),
|
||||
copy_string: None,
|
||||
});
|
||||
out.append(&mut obj.arch.instruction_context(obj, resolved));
|
||||
if let Some(virtual_address) = resolved.symbol.virtual_address {
|
||||
let offset = resolved.ins_ref.address - resolved.symbol.address;
|
||||
out.push(ContextItem::Copy {
|
||||
value: format!("{:x}", virtual_address + offset),
|
||||
label: Some("virtual address".to_string()),
|
||||
copy_string: None,
|
||||
});
|
||||
}
|
||||
for arg in &ins.args {
|
||||
if let InstructionArg::Value(arg) = arg {
|
||||
out.push(ContextItem::Copy { value: arg.to_string(), label: None });
|
||||
out.push(ContextItem::Copy { value: arg.to_string(), label: None, copy_string: None });
|
||||
match arg {
|
||||
InstructionArgValue::Signed(v) => {
|
||||
out.push(ContextItem::Copy { value: v.to_string(), label: None });
|
||||
out.push(ContextItem::Copy {
|
||||
value: v.to_string(),
|
||||
label: None,
|
||||
copy_string: None,
|
||||
});
|
||||
}
|
||||
InstructionArgValue::Unsigned(v) => {
|
||||
out.push(ContextItem::Copy { value: v.to_string(), label: None });
|
||||
out.push(ContextItem::Copy {
|
||||
value: v.to_string(),
|
||||
label: None,
|
||||
copy_string: None,
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -677,7 +691,7 @@ pub fn instruction_hover(
|
||||
let literals = display_ins_data_literals(obj, resolved);
|
||||
if !literals.is_empty() {
|
||||
out.push(HoverItem::Separator);
|
||||
for (literal, label_override) in literals {
|
||||
for (literal, label_override, _) in literals {
|
||||
out.push(HoverItem::Text {
|
||||
label: label_override.unwrap_or_else(|| ty.to_string()),
|
||||
value: format!("{literal:?}"),
|
||||
@@ -871,7 +885,7 @@ pub fn display_ins_data_labels(obj: &Object, resolved: ResolvedInstructionRef) -
|
||||
pub fn display_ins_data_literals(
|
||||
obj: &Object,
|
||||
resolved: ResolvedInstructionRef,
|
||||
) -> Vec<(String, Option<String>)> {
|
||||
) -> Vec<(String, Option<String>, Option<String>)> {
|
||||
let Some(reloc) = resolved.relocation else {
|
||||
return Vec::new();
|
||||
};
|
||||
|
||||
@@ -865,24 +865,6 @@ pub fn hover_items_ui(ui: &mut Ui, items: Vec<HoverItem>, appearance: &Appearanc
|
||||
}
|
||||
}
|
||||
|
||||
/// Escape ASCII characters such as \n or \t, but not Unicode characters such as \u{3000}.
|
||||
/// Suitable for copying to clipboard.
|
||||
fn escape_special_ascii_characters(value: String) -> String {
|
||||
let mut escaped = String::new();
|
||||
escaped.push('"');
|
||||
for c in value.chars() {
|
||||
if c.is_ascii() {
|
||||
for e in c.escape_default() {
|
||||
escaped.push(e);
|
||||
}
|
||||
} else {
|
||||
escaped.push(c);
|
||||
}
|
||||
}
|
||||
escaped.push('"');
|
||||
escaped
|
||||
}
|
||||
|
||||
pub fn context_menu_items_ui(
|
||||
ui: &mut Ui,
|
||||
items: Vec<ContextItem>,
|
||||
@@ -892,7 +874,7 @@ pub fn context_menu_items_ui(
|
||||
let mut ret = None;
|
||||
for item in items {
|
||||
match item {
|
||||
ContextItem::Copy { value, label } => {
|
||||
ContextItem::Copy { value, label, copy_string } => {
|
||||
let mut job = LayoutJob::default();
|
||||
write_text("Copy ", appearance.text_color, &mut job, appearance.code_font.clone());
|
||||
write_text(
|
||||
@@ -912,8 +894,7 @@ pub fn context_menu_items_ui(
|
||||
write_text(")", appearance.text_color, &mut job, appearance.code_font.clone());
|
||||
}
|
||||
if ui.button(job).clicked() {
|
||||
let escaped = escape_special_ascii_characters(value);
|
||||
ui.ctx().copy_text(escaped);
|
||||
ui.ctx().copy_text(copy_string.unwrap_or(value));
|
||||
ui.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -619,11 +619,20 @@ fn symbol_ui(
|
||||
column,
|
||||
)));
|
||||
} else if response.hovered() {
|
||||
ret = Some(if column == 0 {
|
||||
DiffViewAction::SetSymbolHighlight(Some(symbol_idx), symbol_diff.target_symbol, false)
|
||||
let new_highlighted_symbol = if column == 0 {
|
||||
(Some(symbol_idx), symbol_diff.target_symbol)
|
||||
} else {
|
||||
DiffViewAction::SetSymbolHighlight(symbol_diff.target_symbol, Some(symbol_idx), false)
|
||||
});
|
||||
(symbol_diff.target_symbol, Some(symbol_idx))
|
||||
};
|
||||
// Only set the highlight if it changed from the previous frame.
|
||||
// This prevents passive mouse hovers from overriding keyboard actions.
|
||||
if new_highlighted_symbol != state.highlighted_symbol {
|
||||
ret = Some(DiffViewAction::SetSymbolHighlight(
|
||||
new_highlighted_symbol.0,
|
||||
new_highlighted_symbol.1,
|
||||
false,
|
||||
));
|
||||
}
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "objdiff-wasm",
|
||||
"version": "3.5.0",
|
||||
"version": "3.5.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "objdiff-wasm",
|
||||
"version": "3.5.0",
|
||||
"version": "3.5.1",
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^1.9.3",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "objdiff-wasm",
|
||||
"version": "3.5.0",
|
||||
"version": "3.5.1",
|
||||
"description": "A local diffing tool for decompilation projects.",
|
||||
"author": {
|
||||
"name": "Luke Street",
|
||||
|
||||
@@ -273,6 +273,7 @@ impl GuestDisplay for Component {
|
||||
return vec![ContextItem::Copy(ContextItemCopy {
|
||||
value: "Failed to resolve instruction".to_string(),
|
||||
label: Some("error".to_string()),
|
||||
copy_string: None,
|
||||
})];
|
||||
};
|
||||
let ins = match obj.arch.process_instruction(resolved, &diff_config) {
|
||||
@@ -281,6 +282,7 @@ impl GuestDisplay for Component {
|
||||
return vec![ContextItem::Copy(ContextItemCopy {
|
||||
value: e.to_string(),
|
||||
label: Some("error".to_string()),
|
||||
copy_string: None,
|
||||
})];
|
||||
}
|
||||
};
|
||||
@@ -707,8 +709,8 @@ impl From<diff::display::HoverItemColor> for HoverItemColor {
|
||||
impl From<diff::display::ContextItem> for ContextItem {
|
||||
fn from(item: diff::display::ContextItem) -> Self {
|
||||
match item {
|
||||
diff::display::ContextItem::Copy { value, label } => {
|
||||
ContextItem::Copy(ContextItemCopy { value, label })
|
||||
diff::display::ContextItem::Copy { value, label, copy_string } => {
|
||||
ContextItem::Copy(ContextItemCopy { value, label, copy_string })
|
||||
}
|
||||
diff::display::ContextItem::Navigate { label, symbol_index, kind } => {
|
||||
ContextItem::Navigate(ContextItemNavigate {
|
||||
|
||||
@@ -143,6 +143,7 @@ interface display {
|
||||
record context-item-copy {
|
||||
value: string,
|
||||
label: option<string>,
|
||||
copy-string: option<string>,
|
||||
}
|
||||
|
||||
record context-item-navigate {
|
||||
|
||||
Reference in New Issue
Block a user